command/remote: no more remote package
This commit is contained in:
parent
3bf59183b8
commit
2c2b560d7f
|
@ -24,6 +24,7 @@ type Meta struct {
|
||||||
// State read when calling `Context`. This is available after calling
|
// State read when calling `Context`. This is available after calling
|
||||||
// `Context`.
|
// `Context`.
|
||||||
state state.State
|
state state.State
|
||||||
|
stateResult *StateResult
|
||||||
|
|
||||||
// This can be set by the command itself to provide extra hooks.
|
// This can be set by the command itself to provide extra hooks.
|
||||||
extraHooks []terraform.Hook
|
extraHooks []terraform.Hook
|
||||||
|
@ -174,25 +175,45 @@ func (m *Meta) State() (state.State, error) {
|
||||||
return m.state, nil
|
return m.state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result, err := State(m.StateOpts())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.state = result.State
|
||||||
|
m.stateOutPath = result.StatePath
|
||||||
|
m.stateResult = result
|
||||||
|
return m.state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateRaw is used to setup the state manually.
|
||||||
|
func (m *Meta) StateRaw(opts *StateOpts) (*StateResult, error) {
|
||||||
|
result, err := State(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.state = result.State
|
||||||
|
m.stateOutPath = result.StatePath
|
||||||
|
m.stateResult = result
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateOpts returns the default state options
|
||||||
|
func (m *Meta) StateOpts() *StateOpts {
|
||||||
localPath := m.statePath
|
localPath := m.statePath
|
||||||
if localPath == "" {
|
if localPath == "" {
|
||||||
localPath = DefaultStateFilename
|
localPath = DefaultStateFilename
|
||||||
}
|
}
|
||||||
remotePath := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
remotePath := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||||
|
|
||||||
state, statePath, err := State(&StateOpts{
|
return &StateOpts{
|
||||||
LocalPath: localPath,
|
LocalPath: localPath,
|
||||||
LocalPathOut: m.stateOutPath,
|
LocalPathOut: m.stateOutPath,
|
||||||
RemotePath: remotePath,
|
RemotePath: remotePath,
|
||||||
|
RemoteRefresh: true,
|
||||||
BackupPath: m.backupPath,
|
BackupPath: m.backupPath,
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.state = state
|
|
||||||
m.stateOutPath = statePath
|
|
||||||
return state, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIInput returns a UIInput object to be used for asking for input.
|
// UIInput returns a UIInput object to be used for asking for input.
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/remote"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,6 +54,9 @@ func (c *RemoteCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the local state path
|
||||||
|
c.statePath = c.conf.statePath
|
||||||
|
|
||||||
// Populate the various configurations
|
// Populate the various configurations
|
||||||
c.remoteConf.Config = map[string]string{
|
c.remoteConf.Config = map[string]string{
|
||||||
"address": address,
|
"address": address,
|
||||||
|
@ -63,50 +65,56 @@ func (c *RemoteCommand) Run(args []string) int {
|
||||||
"path": path,
|
"path": path,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if have an existing local state file
|
// Get the state information. We specifically request the cache only
|
||||||
haveLocal, err := remote.HaveLocalState()
|
// for the remote state here because it is possible the remote state
|
||||||
if err != nil {
|
// is invalid and we don't want to error.
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to check for local state: %v", err))
|
stateOpts := c.StateOpts()
|
||||||
|
stateOpts.RemoteCacheOnly = true
|
||||||
|
if _, err := c.StateRaw(stateOpts); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have the non-managed state file
|
// Get the local and remote [cached] state
|
||||||
haveNonManaged, err := remote.ExistsFile(c.conf.statePath)
|
localState := c.stateResult.Local.State()
|
||||||
if err != nil {
|
var remoteState *terraform.State
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to check for state file: %v", err))
|
if remote := c.stateResult.Remote; remote != nil {
|
||||||
return 1
|
remoteState = remote.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if remote state is being disabled
|
// Check if remote state is being disabled
|
||||||
if c.conf.disableRemote {
|
if c.conf.disableRemote {
|
||||||
if !haveLocal {
|
if !remoteState.IsRemote() {
|
||||||
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
|
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if haveNonManaged {
|
if !localState.Empty() {
|
||||||
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
|
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
|
||||||
c.conf.statePath))
|
c.conf.statePath))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.disableRemoteState()
|
return c.disableRemoteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is no conflict
|
// Ensure there is no conflict
|
||||||
|
haveCache := !remoteState.Empty()
|
||||||
|
haveLocal := !localState.Empty()
|
||||||
switch {
|
switch {
|
||||||
case haveLocal && haveNonManaged:
|
case haveCache && haveLocal:
|
||||||
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
|
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
|
||||||
c.conf.statePath))
|
c.conf.statePath))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
case !haveLocal && !haveNonManaged:
|
case !haveCache && !haveLocal:
|
||||||
// If we don't have either state file, initialize a blank state file
|
// If we don't have either state file, initialize a blank state file
|
||||||
return c.initBlankState()
|
return c.initBlankState()
|
||||||
|
|
||||||
case haveLocal && !haveNonManaged:
|
case haveCache && !haveLocal:
|
||||||
// Update the remote state target potentially
|
// Update the remote state target potentially
|
||||||
return c.updateRemoteConfig()
|
return c.updateRemoteConfig()
|
||||||
|
|
||||||
case !haveLocal && haveNonManaged:
|
case !haveCache && haveLocal:
|
||||||
// Enable remote state management
|
// Enable remote state management
|
||||||
return c.enableRemoteState()
|
return c.enableRemoteState()
|
||||||
}
|
}
|
||||||
|
@ -117,71 +125,66 @@ func (c *RemoteCommand) Run(args []string) int {
|
||||||
// disableRemoteState is used to disable remote state management,
|
// disableRemoteState is used to disable remote state management,
|
||||||
// and move the state file into place.
|
// and move the state file into place.
|
||||||
func (c *RemoteCommand) disableRemoteState() int {
|
func (c *RemoteCommand) disableRemoteState() int {
|
||||||
// Get the local state
|
if c.stateResult == nil {
|
||||||
local, _, err := remote.ReadLocalState()
|
c.Ui.Error(fmt.Sprintf(
|
||||||
if err != nil {
|
"Internal error. State() must be called internally before remote\n" +
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err))
|
"state can be disabled. Please report this as a bug."))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
if !c.stateResult.State.State().IsRemote() {
|
||||||
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
"Remote state is not enabled. Can't disable remote state."))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
local := c.stateResult.Local
|
||||||
|
remote := c.stateResult.Remote
|
||||||
|
|
||||||
// Ensure we have the latest state before disabling
|
// Ensure we have the latest state before disabling
|
||||||
if c.conf.pullOnDisable {
|
if c.conf.pullOnDisable {
|
||||||
log.Printf("[INFO] Refreshing local state from remote server")
|
log.Printf("[INFO] Refreshing local state from remote server")
|
||||||
change, err := remote.RefreshState(local.Remote)
|
if err := remote.RefreshState(); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
"Failed to refresh from remote state: %v", err))
|
"Failed to refresh from remote state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if we were unable to update
|
// Exit if we were unable to update
|
||||||
if !change.SuccessfulPull() {
|
if change := remote.RefreshResult(); !change.SuccessfulPull() {
|
||||||
c.Ui.Error(fmt.Sprintf("%s", change))
|
c.Ui.Error(fmt.Sprintf("%s", change))
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[INFO] %s", change)
|
log.Printf("[INFO] %s", change)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the local state after the refresh
|
|
||||||
local, _, err = remote.ReadLocalState()
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the remote management, and copy into place
|
// Clear the remote management, and copy into place
|
||||||
local.Remote = nil
|
newState := remote.State()
|
||||||
fh, err := os.Create(c.conf.statePath)
|
newState.Remote = nil
|
||||||
if err != nil {
|
if err := local.WriteState(newState); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to create state file '%s': %v",
|
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
|
||||||
c.conf.statePath, err))
|
c.conf.statePath, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer fh.Close()
|
if err := local.PersistState(); err != nil {
|
||||||
if err := terraform.WriteState(local, fh); err != nil {
|
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %v",
|
|
||||||
c.conf.statePath, err))
|
c.conf.statePath, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the old state file
|
// Remove the old state file
|
||||||
path, err := remote.HiddenStatePath()
|
if err := os.Remove(c.stateResult.RemotePath); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to get local state path: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := os.Remove(path); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
|
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateRemoteConfig is used to verify that the remote configuration
|
// validateRemoteConfig is used to verify that the remote configuration
|
||||||
// we have is valid
|
// we have is valid
|
||||||
func (c *RemoteCommand) validateRemoteConfig() error {
|
func (c *RemoteCommand) validateRemoteConfig() error {
|
||||||
err := remote.ValidConfig(&c.remoteConf)
|
conf := c.remoteConf
|
||||||
|
_, err := remote.NewClient(conf.Type, conf.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
c.Ui.Error(fmt.Sprintf("%s", err))
|
||||||
}
|
}
|
||||||
|
@ -196,18 +199,17 @@ func (c *RemoteCommand) initBlankState() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the hidden directory
|
|
||||||
if err := remote.EnsureDirectory(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a blank state, attach the remote configuration
|
// Make a blank state, attach the remote configuration
|
||||||
blank := terraform.NewState()
|
blank := terraform.NewState()
|
||||||
blank.Remote = &c.remoteConf
|
blank.Remote = &c.remoteConf
|
||||||
|
|
||||||
// Persist the state
|
// Persist the state
|
||||||
if err := remote.PersistState(blank); err != nil {
|
remote := &state.LocalState{Path: c.stateResult.RemotePath}
|
||||||
|
if err := remote.WriteState(blank); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := remote.PersistState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
|
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -225,16 +227,17 @@ func (c *RemoteCommand) updateRemoteConfig() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read in the local state
|
// Read in the local state, which is just the cache of the remote state
|
||||||
local, _, err := remote.ReadLocalState()
|
remote := c.stateResult.Remote.Cache
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the configuration
|
// Update the configuration
|
||||||
local.Remote = &c.remoteConf
|
state := remote.State()
|
||||||
if err := remote.PersistState(local); err != nil {
|
state.Remote = &c.remoteConf
|
||||||
|
if err := remote.WriteState(state); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("%s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := remote.PersistState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
c.Ui.Error(fmt.Sprintf("%s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -252,21 +255,10 @@ func (c *RemoteCommand) enableRemoteState() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the hidden directory
|
// Read the local state
|
||||||
if err := remote.EnsureDirectory(); err != nil {
|
local := c.stateResult.Local
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
if err := local.RefreshState(); err != nil {
|
||||||
return 1
|
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
|
||||||
}
|
|
||||||
|
|
||||||
// Read the provided state file
|
|
||||||
raw, err := ioutil.ReadFile(c.conf.statePath)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to read '%s': %v", c.conf.statePath, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
state, err := terraform.ReadState(bytes.NewReader(raw))
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to decode '%s': %v", c.conf.statePath, err))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,25 +271,31 @@ func (c *RemoteCommand) enableRemoteState() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Writing backup state to: %s", backupPath)
|
log.Printf("[INFO] Writing backup state to: %s", backupPath)
|
||||||
f, err := os.Create(backupPath)
|
backup := &state.LocalState{Path: backupPath}
|
||||||
if err == nil {
|
if err := backup.WriteState(local.State()); err != nil {
|
||||||
err = terraform.WriteState(state, f)
|
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
|
||||||
f.Close()
|
return 1
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err := backup.PersistState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the local configuration, move into place
|
// Update the local configuration, move into place
|
||||||
|
state := local.State()
|
||||||
state.Remote = &c.remoteConf
|
state.Remote = &c.remoteConf
|
||||||
if err := remote.PersistState(state); err != nil {
|
remote := c.stateResult.Remote
|
||||||
|
if err := remote.WriteState(state); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("%s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := remote.PersistState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
c.Ui.Error(fmt.Sprintf("%s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the state file
|
// Remove the original, local state file
|
||||||
log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
|
log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
|
||||||
if err := os.Remove(c.conf.statePath); err != nil {
|
if err := os.Remove(c.conf.statePath); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
|
c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/remote"
|
"github.com/hashicorp/terraform/remote"
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
@ -71,11 +73,6 @@ func TestRemote_disable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we updated
|
// Ensure we updated
|
||||||
// TODO: Should be 10, but WriteState currently
|
|
||||||
// increments incorrectly
|
|
||||||
if newState.Serial != 11 {
|
|
||||||
t.Fatalf("state file not updated: %#v", newState)
|
|
||||||
}
|
|
||||||
if newState.Remote != nil {
|
if newState.Remote != nil {
|
||||||
t.Fatalf("remote configuration not removed")
|
t.Fatalf("remote configuration not removed")
|
||||||
}
|
}
|
||||||
|
@ -96,11 +93,15 @@ func TestRemote_disable_noPull(t *testing.T) {
|
||||||
s = terraform.NewState()
|
s = terraform.NewState()
|
||||||
s.Serial = 5
|
s.Serial = 5
|
||||||
s.Remote = conf
|
s.Remote = conf
|
||||||
if err := remote.EnsureDirectory(); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
// Write the state
|
||||||
|
statePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename)
|
||||||
|
state := &state.LocalState{Path: statePath}
|
||||||
|
if err := state.WriteState(s); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
if err := remote.PersistState(s); err != nil {
|
if err := state.PersistState(); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -140,12 +141,6 @@ func TestRemote_disable_noPull(t *testing.T) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we DIDNT updated
|
|
||||||
// TODO: Should be 5, but WriteState currently increments
|
|
||||||
// this which is incorrect.
|
|
||||||
if newState.Serial != 7 {
|
|
||||||
t.Fatalf("state file updated: %#v", newState)
|
|
||||||
}
|
|
||||||
if newState.Remote != nil {
|
if newState.Remote != nil {
|
||||||
t.Fatalf("remote configuration not removed")
|
t.Fatalf("remote configuration not removed")
|
||||||
}
|
}
|
||||||
|
|
114
command/state.go
114
command/state.go
|
@ -21,7 +21,12 @@ type StateOpts struct {
|
||||||
LocalPathOut string
|
LocalPathOut string
|
||||||
|
|
||||||
// RemotePath is the path where the remote state cache would be.
|
// RemotePath is the path where the remote state cache would be.
|
||||||
|
//
|
||||||
|
// RemoteCache, if true, will set the result to only be the cache
|
||||||
|
// and not backed by any real durable storage.
|
||||||
RemotePath string
|
RemotePath string
|
||||||
|
RemoteCacheOnly bool
|
||||||
|
RemoteRefresh bool
|
||||||
|
|
||||||
// BackupPath is the path where the backup will be placed. If not set,
|
// BackupPath is the path where the backup will be placed. If not set,
|
||||||
// it is assumed to be the path where the state is stored locally
|
// it is assumed to be the path where the state is stored locally
|
||||||
|
@ -29,25 +34,74 @@ type StateOpts struct {
|
||||||
BackupPath string
|
BackupPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateResult is the result of calling State and holds various different
|
||||||
|
// State implementations so they can be accessed directly.
|
||||||
|
type StateResult struct {
|
||||||
|
// State is the final outer state that should be used for all
|
||||||
|
// _real_ reads/writes.
|
||||||
|
//
|
||||||
|
// StatePath is the local path where the state will be stored or
|
||||||
|
// cached, no matter whether State is local or remote.
|
||||||
|
State state.State
|
||||||
|
StatePath string
|
||||||
|
|
||||||
|
// Local and Remote are the local/remote state implementations, raw
|
||||||
|
// and unwrapped by any backups. The paths here are the paths where
|
||||||
|
// these state files would be saved.
|
||||||
|
Local *state.LocalState
|
||||||
|
LocalPath string
|
||||||
|
Remote *state.CacheState
|
||||||
|
RemotePath string
|
||||||
|
}
|
||||||
|
|
||||||
// State returns the proper state.State implementation to represent the
|
// State returns the proper state.State implementation to represent the
|
||||||
// current environment.
|
// current environment.
|
||||||
//
|
//
|
||||||
// localPath is the path to where state would be if stored locally.
|
// 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
|
// dataDir is the path to the local data directory where the remote state
|
||||||
// cache would be stored.
|
// cache would be stored.
|
||||||
func State(opts *StateOpts) (state.State, string, error) {
|
func State(opts *StateOpts) (*StateResult, error) {
|
||||||
var result state.State
|
result := new(StateResult)
|
||||||
var resultPath string
|
|
||||||
|
|
||||||
// Get the remote state cache path
|
// Get the remote state cache path
|
||||||
if opts.RemotePath != "" {
|
if opts.RemotePath != "" {
|
||||||
|
result.RemotePath = opts.RemotePath
|
||||||
|
|
||||||
|
var remote *state.CacheState
|
||||||
|
if opts.RemoteCacheOnly {
|
||||||
|
// Setup the in-memory state
|
||||||
|
ls := &state.LocalState{Path: opts.RemotePath}
|
||||||
|
if err := ls.RefreshState(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
is := &state.InmemState{}
|
||||||
|
is.WriteState(ls.State())
|
||||||
|
|
||||||
|
// Setupt he remote state, cache-only, and refresh it so that
|
||||||
|
// we have access to the state right away.
|
||||||
|
remote = &state.CacheState{
|
||||||
|
Cache: ls,
|
||||||
|
Durable: is,
|
||||||
|
}
|
||||||
|
if err := remote.RefreshState(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if _, err := os.Stat(opts.RemotePath); err == nil {
|
if _, err := os.Stat(opts.RemotePath); err == nil {
|
||||||
// We have a remote state, initialize that.
|
// We have a remote state, initialize that.
|
||||||
result, err = remoteStateFromPath(opts.RemotePath)
|
remote, err = remoteStateFromPath(
|
||||||
|
opts.RemotePath,
|
||||||
|
opts.RemoteRefresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
resultPath = opts.RemotePath
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remote != nil {
|
||||||
|
result.State = remote
|
||||||
|
result.StatePath = opts.RemotePath
|
||||||
|
result.Remote = remote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,22 +111,20 @@ func State(opts *StateOpts) (state.State, string, error) {
|
||||||
Path: opts.LocalPath,
|
Path: opts.LocalPath,
|
||||||
PathOut: opts.LocalPathOut,
|
PathOut: opts.LocalPathOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always store it in the result even if we're not using it
|
||||||
|
result.Local = local
|
||||||
|
result.LocalPath = local.Path
|
||||||
|
if local.PathOut != "" {
|
||||||
|
result.LocalPath = local.PathOut
|
||||||
|
}
|
||||||
|
|
||||||
err := local.RefreshState()
|
err := local.RefreshState()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
isNotExist := false
|
if result.State != nil && !result.State.State().Empty() {
|
||||||
errwrap.Walk(err, func(e error) {
|
|
||||||
if !isNotExist && os.IsNotExist(e) {
|
|
||||||
isNotExist = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if isNotExist {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if result != nil {
|
|
||||||
if !local.State().Empty() {
|
if !local.State().Empty() {
|
||||||
// We already have a remote state... that is an error.
|
// We already have a remote state... that is an error.
|
||||||
return nil, "", fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Remote state found, but state file '%s' also present.",
|
"Remote state found, but state file '%s' also present.",
|
||||||
opts.LocalPath)
|
opts.LocalPath)
|
||||||
}
|
}
|
||||||
|
@ -82,34 +134,34 @@ func State(opts *StateOpts) (state.State, string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", errwrap.Wrapf(
|
return nil, errwrap.Wrapf(
|
||||||
"Error reading local state: {{err}}", err)
|
"Error reading local state: {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if local != nil {
|
if local != nil {
|
||||||
result = local
|
result.State = local
|
||||||
resultPath = opts.LocalPath
|
result.StatePath = opts.LocalPath
|
||||||
if opts.LocalPathOut != "" {
|
if opts.LocalPathOut != "" {
|
||||||
resultPath = opts.LocalPathOut
|
result.StatePath = opts.LocalPathOut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a result, make sure to back it up
|
// If we have a result, make sure to back it up
|
||||||
if result != nil {
|
if result.State != nil {
|
||||||
backupPath := resultPath + DefaultBackupExtention
|
backupPath := result.StatePath + DefaultBackupExtention
|
||||||
if opts.BackupPath != "" {
|
if opts.BackupPath != "" {
|
||||||
backupPath = opts.BackupPath
|
backupPath = opts.BackupPath
|
||||||
}
|
}
|
||||||
|
|
||||||
result = &state.BackupState{
|
result.State = &state.BackupState{
|
||||||
Real: result,
|
Real: result.State,
|
||||||
Path: backupPath,
|
Path: backupPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return whatever state we have
|
// Return whatever state we have
|
||||||
return result, resultPath, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFromPlan gets our state from the plan.
|
// StateFromPlan gets our state from the plan.
|
||||||
|
@ -147,7 +199,7 @@ func StateFromPlan(
|
||||||
|
|
||||||
func remoteState(
|
func remoteState(
|
||||||
local *terraform.State,
|
local *terraform.State,
|
||||||
localPath string, refresh bool) (state.State, error) {
|
localPath string, refresh bool) (*state.CacheState, error) {
|
||||||
// If there is no remote settings, it is an error
|
// If there is no remote settings, it is an error
|
||||||
if local.Remote == nil {
|
if local.Remote == nil {
|
||||||
return nil, fmt.Errorf("Remote state cache has no remote info")
|
return nil, fmt.Errorf("Remote state cache has no remote info")
|
||||||
|
@ -199,7 +251,7 @@ func remoteState(
|
||||||
return cache, nil
|
return cache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func remoteStateFromPath(path string) (state.State, error) {
|
func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) {
|
||||||
// First create the local state for the path
|
// First create the local state for the path
|
||||||
local := &state.LocalState{Path: path}
|
local := &state.LocalState{Path: path}
|
||||||
if err := local.RefreshState(); err != nil {
|
if err := local.RefreshState(); err != nil {
|
||||||
|
@ -207,5 +259,5 @@ func remoteStateFromPath(path string) (state.State, error) {
|
||||||
}
|
}
|
||||||
localState := local.State()
|
localState := local.State()
|
||||||
|
|
||||||
return remoteState(localState, path, true)
|
return remoteState(localState, path, refresh)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue