Merge pull request #8463 from hashicorp/b-read-state-validate

Validate the state on load
pull/8503/head
Mitchell Hashimoto 10 years ago committed by GitHub
commit 3c2db3d572

@ -14,6 +14,7 @@ import (
"strings"
"sync"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/config"
"github.com/mitchellh/copystructure"
@ -247,6 +248,46 @@ func (s *State) IsRemote() bool {
return true
}
// Validate validates the integrity of this state file.
//
// Certain properties of the statefile are expected by Terraform in order
// to behave properly. The core of Terraform will assume that once it
// receives a State structure that it has been validated. This validation
// check should be called to ensure that.
//
// If this returns an error, then the user should be notified. The error
// response will include detailed information on the nature of the error.
func (s *State) Validate() error {
var result error
// !!!! FOR DEVELOPERS !!!!
//
// Any errors returned from this Validate function will BLOCK TERRAFORM
// from loading a state file. Therefore, this should only contain checks
// that are only resolvable through manual intervention.
//
// !!!! FOR DEVELOPERS !!!!
// Make sure there are no duplicate module states. We open a new
// block here so we can use basic variable names and future validations
// can do the same.
{
found := make(map[string]struct{})
for _, ms := range s.Modules {
key := strings.Join(ms.Path, ".")
if _, ok := found[key]; ok {
result = multierror.Append(result, fmt.Errorf(
strings.TrimSpace(stateValidateErrMultiModule), key))
continue
}
found[key] = struct{}{}
}
}
return result
}
// Remove removes the item in the state at the given address, returning
// any errors that may have occurred.
//
@ -1549,6 +1590,7 @@ func ReadState(src io.Reader) (*State, error) {
return nil, fmt.Errorf("Decoding state file version failed: %v", err)
}
var result *State
switch versionIdentifier.Version {
case 0:
return nil, fmt.Errorf("State version 0 is not supported as JSON.")
@ -1570,7 +1612,7 @@ func ReadState(src io.Reader) (*State, error) {
// increment the Serial whenever we upgrade state
v3State.Serial++
return v3State, nil
result = v3State
case 2:
v2State, err := ReadStateV2(jsonBytes)
if err != nil {
@ -1582,18 +1624,30 @@ func ReadState(src io.Reader) (*State, error) {
}
v3State.Serial++
return v3State, nil
result = v3State
case 3:
v3State, err := ReadStateV3(jsonBytes)
if err != nil {
return nil, err
}
return v3State, nil
result = v3State
default:
return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.",
SemVersion.String(), versionIdentifier.Version)
}
// If we reached this place we must have a result set
if result == nil {
panic("resulting state in load not set, assertion failed")
}
// Validate the state file is valid
if err := result.Validate(); err != nil {
return nil, err
}
return result, nil
}
func ReadStateV1(jsonBytes []byte) (*stateV1, error) {
@ -1758,3 +1812,12 @@ func (s moduleStateSort) Less(i, j int) bool {
func (s moduleStateSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
const stateValidateErrMultiModule = `
Multiple modules with the same path: %s
This means that there are multiple entries in the "modules" field
in your state file that point to the same module. This will cause Terraform
to behave in unexpected and error prone ways and is invalid. Please back up
and modify your state file manually to resolve this.
`

@ -10,6 +10,42 @@ import (
"github.com/hashicorp/terraform/config"
)
func TestStateValidate(t *testing.T) {
cases := map[string]struct {
In *State
Err bool
}{
"empty state": {
&State{},
false,
},
"multiple modules": {
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root", "foo"},
},
&ModuleState{
Path: []string{"root", "foo"},
},
},
},
true,
},
}
for name, tc := range cases {
// Init the state
tc.In.init()
err := tc.In.Validate()
if (err != nil) != tc.Err {
t.Fatalf("%s: err: %s", name, err)
}
}
}
func TestStateAddModule(t *testing.T) {
cases := []struct {
In [][]string

Loading…
Cancel
Save