command/state: update and fix the state push and pull
This commit is contained in:
parent
17b883f592
commit
7fbd93b5cd
|
@ -1,9 +1,12 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
@ -34,37 +37,34 @@ func (c *StatePullCommand) Run(args []string) int {
|
|||
|
||||
// Get the state
|
||||
env := c.Workspace()
|
||||
state, err := b.StateMgr(env)
|
||||
stateMgr, err := b.StateMgr(env)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := state.RefreshState(); err != nil {
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
s := state.State()
|
||||
if s == nil {
|
||||
state := stateMgr.State()
|
||||
if state == nil {
|
||||
// Output on "error" so it shows up on stderr
|
||||
c.Ui.Error("Empty state (no state)")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
c.Ui.Error("state pull not yet updated for new state types")
|
||||
return 1
|
||||
// Get the state file.
|
||||
stateFile := statemgr.StateFile(stateMgr, state)
|
||||
|
||||
/*
|
||||
var buf bytes.Buffer
|
||||
if err := terraform.WriteState(s, &buf); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(buf.String())
|
||||
*/
|
||||
var buf bytes.Buffer
|
||||
err = statefile.Write(stateFile, &buf)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(buf.String())
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/copy"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestStatePull(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("state-pull-backend"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Create some legacy remote state
|
||||
legacyState := testState()
|
||||
backendState, srv := testRemoteState(t, legacyState, 200)
|
||||
defer srv.Close()
|
||||
testStateFileRemote(t, backendState)
|
||||
expected, err := ioutil.ReadFile("local-state.tfstate")
|
||||
if err != nil {
|
||||
t.Fatalf("error reading state: %v", err)
|
||||
}
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
|
@ -31,9 +36,8 @@ func TestStatePull(t *testing.T) {
|
|||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
expected := "test_instance.foo"
|
||||
actual := ui.OutputWriter.String()
|
||||
if !strings.Contains(actual, expected) {
|
||||
actual := ui.OutputWriter.Bytes()
|
||||
if bytes.Equal(actual, expected) {
|
||||
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
@ -31,86 +36,76 @@ func (c *StatePushCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Error("state push not yet updated for new state types")
|
||||
return 1
|
||||
|
||||
/*
|
||||
// Determine our reader for the input state. This is the filepath
|
||||
// or stdin if "-" is given.
|
||||
var r io.Reader = os.Stdin
|
||||
if args[0] != "-" {
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// Note: we don't need to defer a Close here because we do a close
|
||||
// automatically below directly after the read.
|
||||
|
||||
r = f
|
||||
}
|
||||
|
||||
// Read the state
|
||||
sourceState, err := terraform.ReadState(r)
|
||||
if c, ok := r.(io.Closer); ok {
|
||||
// Close the reader if possible right now since we're done with it.
|
||||
c.Close()
|
||||
}
|
||||
// Determine our reader for the input state. This is the filepath
|
||||
// or stdin if "-" is given.
|
||||
var r io.Reader = os.Stdin
|
||||
if args[0] != "-" {
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// Load the backend
|
||||
b, backendDiags := c.Backend(nil)
|
||||
if backendDiags.HasErrors() {
|
||||
c.showDiagnostics(backendDiags)
|
||||
// Note: we don't need to defer a Close here because we do a close
|
||||
// automatically below directly after the read.
|
||||
|
||||
r = f
|
||||
}
|
||||
|
||||
// Read the state
|
||||
srcStateFile, err := statefile.Read(r)
|
||||
if c, ok := r.(io.Closer); ok {
|
||||
// Close the reader if possible right now since we're done with it.
|
||||
c.Close()
|
||||
}
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Load the backend
|
||||
b, backendDiags := c.Backend(nil)
|
||||
if backendDiags.HasErrors() {
|
||||
c.showDiagnostics(backendDiags)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the state
|
||||
env := c.Workspace()
|
||||
stateMgr, err := b.StateMgr(env)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||
return 1
|
||||
}
|
||||
dstState := stateMgr.State()
|
||||
|
||||
// If we're not forcing, then perform safety checks
|
||||
if !flagForce && !dstState.Empty() {
|
||||
dstStateFile := statemgr.StateFile(stateMgr, dstState)
|
||||
|
||||
if dstStateFile.Lineage != srcStateFile.Lineage {
|
||||
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the state
|
||||
env := c.Workspace()
|
||||
state, err := b.StateMgr(env)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := state.RefreshState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||
if dstStateFile.Serial > srcStateFile.Serial {
|
||||
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
dstState := state.State()
|
||||
|
||||
// If we're not forcing, then perform safety checks
|
||||
if !flagForce && !dstState.Empty() {
|
||||
if !dstState.SameLineage(sourceState) {
|
||||
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
|
||||
return 1
|
||||
}
|
||||
|
||||
age, err := dstState.CompareAges(sourceState)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if age == terraform.StateAgeReceiverNewer {
|
||||
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite it
|
||||
if err := state.WriteState(sourceState); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := state.PersistState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||
return 1
|
||||
}
|
||||
*/
|
||||
// Overwrite it
|
||||
if err := stateMgr.WriteState(srcStateFile.State); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := stateMgr.PersistState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ func TestStatePush_replaceMatchStdin(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
args := []string{"-"}
|
||||
args := []string{"-force", "-"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ func TestStatePush_serialNewer(t *testing.T) {
|
|||
|
||||
args := []string{"replace.tfstate"}
|
||||
if code := c.Run(args); code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
t.Fatalf("bad: %d", code)
|
||||
}
|
||||
|
||||
actual := testStateRead(t, "local-state.tfstate")
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 0,
|
||||
"lineage": "666f9301-7e65-4b19-ae23-71184bb19b03",
|
||||
"backend": {
|
||||
"type": "local",
|
||||
"config": {
|
||||
"path": "local-state.tfstate",
|
||||
"workspace_dir": null
|
||||
},
|
||||
"hash": 4282859327
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {},
|
||||
"resources": {},
|
||||
"depends_on": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"version": 4,
|
||||
"terraform_version": "0.12.0",
|
||||
"serial": 7,
|
||||
"lineage": "configuredUnchanged",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "a",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "8521602373864259745",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
terraform {
|
||||
backend "local" {
|
||||
path = "local-state.tfstate"
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@
|
|||
"backend": {
|
||||
"type": "local",
|
||||
"config": {
|
||||
"path": "local-state.tfstate"
|
||||
"path": "local-state.tfstate",
|
||||
"workspace_dir": null
|
||||
},
|
||||
"hash": 9073424445967744180
|
||||
"hash": 4282859327
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 0,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 0,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "b",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "9051675049789185374",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
"backend": {
|
||||
"type": "local",
|
||||
"config": {
|
||||
"path": "local-state.tfstate"
|
||||
"path": "local-state.tfstate",
|
||||
"workspace_dir": null
|
||||
},
|
||||
"hash": 9073424445967744180
|
||||
"hash": 4282859327
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 1,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 1,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "a",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "8521602373864259745",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 2,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 2,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "b",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "9051675049789185374",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
"backend": {
|
||||
"type": "local",
|
||||
"config": {
|
||||
"path": "local-state.tfstate"
|
||||
"path": "local-state.tfstate",
|
||||
"workspace_dir": null
|
||||
},
|
||||
"hash": 9073424445967744180
|
||||
"hash": 4282859327
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 3,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 3,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "a",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "8521602373864259745",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 2,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 2,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "b",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "9051675049789185374",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
"backend": {
|
||||
"type": "local",
|
||||
"config": {
|
||||
"path": "local-state.tfstate"
|
||||
"path": "local-state.tfstate",
|
||||
"workspace_dir": null
|
||||
},
|
||||
"hash": 9073424445967744180
|
||||
"hash": 4282859327
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 1,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 1,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "a",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "8521602373864259745",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 2,
|
||||
"lineage": "hello"
|
||||
"version": 4,
|
||||
"serial": 2,
|
||||
"lineage": "hello",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "null_resource",
|
||||
"name": "b",
|
||||
"provider": "provider.null",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"id": "9051675049789185374",
|
||||
"triggers": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,8 +5,38 @@ package statemgr
|
|||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// NewStateFile creates a new statefile.File object, with a newly-minted
|
||||
// lineage identifier and serial 0, and returns a pointer to it.
|
||||
func NewStateFile() *statefile.File {
|
||||
return &statefile.File{
|
||||
Lineage: NewLineage(),
|
||||
TerraformVersion: version.SemVer,
|
||||
}
|
||||
}
|
||||
|
||||
// StateFile is a special helper to obtain a statefile representation
|
||||
// of a state snapshot that can be written later by a call
|
||||
func StateFile(mgr Storage, state *states.State) *statefile.File {
|
||||
ret := &statefile.File{
|
||||
State: state.DeepCopy(),
|
||||
TerraformVersion: version.SemVer,
|
||||
}
|
||||
|
||||
// If the given manager uses snapshot metadata then we'll save that
|
||||
// in our file so we can check it again during WritePlannedStateUpdate.
|
||||
if mr, ok := mgr.(PersistentMeta); ok {
|
||||
m := mr.StateSnapshotMeta()
|
||||
ret.Lineage = m.Lineage
|
||||
ret.Serial = m.Serial
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// RefreshAndRead refreshes the persistent snapshot in the given state manager
|
||||
// and then returns it.
|
||||
//
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package statemgr
|
|
@ -4,9 +4,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// NewLineage generates a new lineage identifier string. A lineage identifier
|
||||
|
@ -21,12 +18,3 @@ func NewLineage() string {
|
|||
}
|
||||
return lineage
|
||||
}
|
||||
|
||||
// NewStateFile creates a new statefile.File object, with a newly-minted
|
||||
// lineage identifier and serial 0, and returns a pointer to it.
|
||||
func NewStateFile() *statefile.File {
|
||||
return &statefile.File{
|
||||
Lineage: NewLineage(),
|
||||
TerraformVersion: version.SemVer,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue