diff --git a/terraform/state.go b/terraform/state.go index 02ee9cf4f..63dbe95ef 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -1384,18 +1384,19 @@ type jsonStateVersionIdentifier struct { func ReadState(src io.Reader) (*State, error) { buf := bufio.NewReader(src) - // Check if this is a V0 format - start, err := buf.Peek(len(stateFormatMagic)) + // Check if this is a V0 format - the magic bytes at the start of the file + // should be "tfstate" if so. We no longer support upgrading this type of + // state but display an error message explaining to a user how they can + // upgrade via the 0.6.x series. + start, err := buf.Peek(len("tfstate")) if err != nil { return nil, fmt.Errorf("Failed to check for magic bytes: %v", err) } - if string(start) == stateFormatMagic { - // Read the old state - old, err := ReadStateV0(buf) - if err != nil { - return nil, err - } - return old.upgrade() + if string(start) == "tfstate" { + return nil, fmt.Errorf("Terraform 0.7 no longer supports upgrading the binary state\n" + + "format which was used prior to Terraform 0.3. Please upgrade\n" + + "this state file using Terraform 0.6.16 prior to using it with\n" + + "Terraform 0.7.") } // If we are JSON we buffer the whole thing in memory so we can read it twice. diff --git a/terraform/state_test.go b/terraform/state_test.go index e3ee69137..f265074aa 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -1184,36 +1184,6 @@ func TestReadUpgradeStateV1toV2_outputs(t *testing.T) { } } -func TestReadUpgradeState(t *testing.T) { - state := &StateV0{ - Resources: map[string]*ResourceStateV0{ - "foo": &ResourceStateV0{ - ID: "bar", - }, - }, - } - buf := new(bytes.Buffer) - if err := testWriteStateV0(state, buf); err != nil { - t.Fatalf("err: %s", err) - } - - // ReadState should transparently detect the old - // version and upgrade up so the latest. - actual, err := ReadState(buf) - if err != nil { - t.Fatalf("err: %s", err) - } - - upgraded, err := state.upgrade() - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(actual, upgraded) { - t.Fatalf("bad: %#v", actual) - } -} - func TestReadWriteState(t *testing.T) { state := &State{ Serial: 9, @@ -1385,73 +1355,6 @@ func TestWriteStateTFVersion(t *testing.T) { } } -func TestUpgradeV0State(t *testing.T) { - old := &StateV0{ - Outputs: map[string]string{ - "ip": "127.0.0.1", - }, - Resources: map[string]*ResourceStateV0{ - "foo": &ResourceStateV0{ - Type: "test_resource", - ID: "bar", - Attributes: map[string]string{ - "key": "val", - }, - }, - "bar": &ResourceStateV0{ - Type: "test_resource", - ID: "1234", - Attributes: map[string]string{ - "a": "b", - }, - }, - }, - Tainted: map[string]struct{}{ - "bar": struct{}{}, - }, - } - state, err := old.upgrade() - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(state.Modules) != 1 { - t.Fatalf("should only have root module: %#v", state.Modules) - } - root := state.RootModule() - - if len(root.Outputs) != 1 { - t.Fatalf("bad outputs: %v", root.Outputs) - } - if root.Outputs["ip"].Value != "127.0.0.1" { - t.Fatalf("bad outputs: %v", root.Outputs) - } - - if len(root.Resources) != 2 { - t.Fatalf("bad resources: %v", root.Resources) - } - - foo := root.Resources["foo"] - if foo.Type != "test_resource" { - t.Fatalf("bad: %#v", foo) - } - if foo.Primary == nil || foo.Primary.ID != "bar" || - foo.Primary.Attributes["key"] != "val" { - t.Fatalf("bad: %#v", foo) - } - if foo.Primary.Tainted { - t.Fatalf("bad: %#v", foo) - } - - bar := root.Resources["bar"] - if bar.Type != "test_resource" { - t.Fatalf("bad: %#v", bar) - } - if !bar.Primary.Tainted { - t.Fatalf("bad: %#v", bar) - } -} - func TestParseResourceStateKey(t *testing.T) { cases := []struct { Input string diff --git a/terraform/state_v0.go b/terraform/state_v0.go deleted file mode 100644 index 33bc9b238..000000000 --- a/terraform/state_v0.go +++ /dev/null @@ -1,367 +0,0 @@ -package terraform - -import ( - "bytes" - "encoding/gob" - "errors" - "fmt" - "io" - "sort" - "strings" - "sync" - - "log" - - "github.com/hashicorp/terraform/config" -) - -// The format byte is prefixed into the state file format so that we have -// the ability in the future to change the file format if we want for any -// reason. -const ( - stateFormatMagic = "tfstate" - stateFormatVersion byte = 1 -) - -// StateV0 is used to represent the state of Terraform files before -// 0.3. It is automatically upgraded to a modern State representation -// on start. -type StateV0 struct { - Outputs map[string]string - Resources map[string]*ResourceStateV0 - Tainted map[string]struct{} - - once sync.Once -} - -func (s *StateV0) init() { - s.once.Do(func() { - if s.Resources == nil { - s.Resources = make(map[string]*ResourceStateV0) - } - - if s.Tainted == nil { - s.Tainted = make(map[string]struct{}) - } - }) -} - -func (s *StateV0) deepcopy() *StateV0 { - result := new(StateV0) - result.init() - if s != nil { - for k, v := range s.Resources { - result.Resources[k] = v - } - for k, v := range s.Tainted { - result.Tainted[k] = v - } - } - - return result -} - -// prune is a helper that removes any empty IDs from the state -// and cleans it up in general. -func (s *StateV0) prune() { - for k, v := range s.Resources { - if v.ID == "" { - delete(s.Resources, k) - } - } -} - -// Orphans returns a list of keys of resources that are in the State -// but aren't present in the configuration itself. Hence, these keys -// represent the state of resources that are orphans. -func (s *StateV0) Orphans(c *config.Config) []string { - keys := make(map[string]struct{}) - for k, _ := range s.Resources { - keys[k] = struct{}{} - } - - for _, r := range c.Resources { - delete(keys, r.Id()) - - for k, _ := range keys { - if strings.HasPrefix(k, r.Id()+".") { - delete(keys, k) - } - } - } - - result := make([]string, 0, len(keys)) - for k, _ := range keys { - result = append(result, k) - } - - return result -} - -func (s *StateV0) String() string { - if len(s.Resources) == 0 { - return "" - } - - var buf bytes.Buffer - - names := make([]string, 0, len(s.Resources)) - for name, _ := range s.Resources { - names = append(names, name) - } - sort.Strings(names) - - for _, k := range names { - rs := s.Resources[k] - id := rs.ID - if id == "" { - id = "" - } - - taintStr := "" - if _, ok := s.Tainted[k]; ok { - taintStr = " (tainted)" - } - - buf.WriteString(fmt.Sprintf("%s:%s\n", k, taintStr)) - buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) - - attrKeys := make([]string, 0, len(rs.Attributes)) - for ak, _ := range rs.Attributes { - if ak == "id" { - continue - } - - attrKeys = append(attrKeys, ak) - } - sort.Strings(attrKeys) - - for _, ak := range attrKeys { - av := rs.Attributes[ak] - buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av)) - } - - if len(rs.Dependencies) > 0 { - buf.WriteString(fmt.Sprintf("\n Dependencies:\n")) - for _, dep := range rs.Dependencies { - buf.WriteString(fmt.Sprintf(" %s\n", dep.ID)) - } - } - } - - if len(s.Outputs) > 0 { - buf.WriteString("\nOutputs:\n\n") - - ks := make([]string, 0, len(s.Outputs)) - for k, _ := range s.Outputs { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - v := s.Outputs[k] - buf.WriteString(fmt.Sprintf("%s = %s\n", k, v)) - } - } - - return buf.String() -} - -/// ResourceState holds the state of a resource that is used so that -// a provider can find and manage an existing resource as well as for -// storing attributes that are uesd to populate variables of child -// resources. -// -// Attributes has attributes about the created resource that are -// queryable in interpolation: "${type.id.attr}" -// -// Extra is just extra data that a provider can return that we store -// for later, but is not exposed in any way to the user. -type ResourceStateV0 struct { - // This is filled in and managed by Terraform, and is the resource - // type itself such as "mycloud_instance". If a resource provider sets - // this value, it won't be persisted. - Type string - - // The attributes below are all meant to be filled in by the - // resource providers themselves. Documentation for each are above - // each element. - - // A unique ID for this resource. This is opaque to Terraform - // and is only meant as a lookup mechanism for the providers. - ID string - - // Attributes are basic information about the resource. Any keys here - // are accessible in variable format within Terraform configurations: - // ${resourcetype.name.attribute}. - Attributes map[string]string - - // ConnInfo is used for the providers to export information which is - // used to connect to the resource for provisioning. For example, - // this could contain SSH or WinRM credentials. - ConnInfo map[string]string - - // Extra information that the provider can store about a resource. - // This data is opaque, never shown to the user, and is sent back to - // the provider as-is for whatever purpose appropriate. - Extra map[string]interface{} - - // Dependencies are a list of things that this resource relies on - // existing to remain intact. For example: an AWS instance might - // depend on a subnet (which itself might depend on a VPC, and so - // on). - // - // Terraform uses this information to build valid destruction - // orders and to warn the user if they're destroying a resource that - // another resource depends on. - // - // Things can be put into this list that may not be managed by - // Terraform. If Terraform doesn't find a matching ID in the - // overall state, then it assumes it isn't managed and doesn't - // worry about it. - Dependencies []ResourceDependency -} - -// MergeDiff takes a ResourceDiff and merges the attributes into -// this resource state in order to generate a new state. This new -// state can be used to provide updated attribute lookups for -// variable interpolation. -// -// If the diff attribute requires computing the value, and hence -// won't be available until apply, the value is replaced with the -// computeID. -func (s *ResourceStateV0) MergeDiff(d *InstanceDiff) *ResourceStateV0 { - var result ResourceStateV0 - if s != nil { - result = *s - } - - result.Attributes = make(map[string]string) - if s != nil { - for k, v := range s.Attributes { - result.Attributes[k] = v - } - } - if d != nil { - for k, diff := range d.Attributes { - if diff.NewRemoved { - delete(result.Attributes, k) - continue - } - if diff.NewComputed { - result.Attributes[k] = config.UnknownVariableValue - continue - } - - result.Attributes[k] = diff.New - } - } - - return &result -} - -func (s *ResourceStateV0) GoString() string { - return fmt.Sprintf("*%#v", *s) -} - -// ResourceDependency maps a resource to another resource that it -// depends on to remain intact and uncorrupted. -type ResourceDependency struct { - // ID of the resource that we depend on. This ID should map - // directly to another ResourceState's ID. - ID string -} - -// ReadStateV0 reads a state structure out of a reader in the format that -// was written by WriteState. -func ReadStateV0(src io.Reader) (*StateV0, error) { - var result *StateV0 - var err error - n := 0 - - // Verify the magic bytes - magic := make([]byte, len(stateFormatMagic)) - for n < len(magic) { - n, err = src.Read(magic[n:]) - if err != nil { - return nil, fmt.Errorf("error while reading magic bytes: %s", err) - } - } - if string(magic) != stateFormatMagic { - return nil, fmt.Errorf("not a valid state file") - } - - // Verify the version is something we can read - var formatByte [1]byte - n, err = src.Read(formatByte[:]) - if err != nil { - return nil, err - } - if n != len(formatByte) { - return nil, errors.New("failed to read state version byte") - } - - if formatByte[0] != stateFormatVersion { - return nil, fmt.Errorf("unknown state file version: %d", formatByte[0]) - } - - // Decode - dec := gob.NewDecoder(src) - if err := dec.Decode(&result); err != nil { - return nil, err - } - - return result, nil -} - -// upgradeV0State is used to upgrade a V0 state representation -// into a State (current) representation. -func (old *StateV0) upgrade() (*State, error) { - s := &State{} - s.init() - - // Old format had no modules, so we migrate everything - // directly into the root module. - root := s.RootModule() - - // Copy the outputs, first converting them to map[string]interface{} - oldOutputs := make(map[string]*OutputState, len(old.Outputs)) - for key, value := range old.Outputs { - oldOutputs[key] = &OutputState{ - Type: "string", - Sensitive: false, - Value: value, - } - } - root.Outputs = oldOutputs - - // Upgrade the resources - for id, rs := range old.Resources { - newRs := &ResourceState{ - Type: rs.Type, - } - root.Resources[id] = newRs - - // Migrate to an instance state - newRs.Primary = &InstanceState{ - ID: rs.ID, - Attributes: rs.Attributes, - } - - // Check if old resource was tainted - if _, ok := old.Tainted[id]; ok { - newRs.Primary.Tainted = true - } - - // Warn if the resource uses Extra, as there is - // no upgrade path for this! Now totally deprecated. - if len(rs.Extra) > 0 { - log.Printf( - "[WARN] Resource %s uses deprecated attribute "+ - "storage, state file upgrade may be incomplete.", - rs.ID, - ) - } - } - return s, nil -} diff --git a/terraform/state_v0_test.go b/terraform/state_v0_test.go deleted file mode 100644 index 04f84545c..000000000 --- a/terraform/state_v0_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package terraform - -import ( - "bytes" - "encoding/gob" - "errors" - "io" - "reflect" - "sync" - "testing" - - "github.com/mitchellh/hashstructure" -) - -func TestReadWriteStateV0(t *testing.T) { - state := &StateV0{ - Resources: map[string]*ResourceStateV0{ - "foo": &ResourceStateV0{ - ID: "bar", - ConnInfo: map[string]string{ - "type": "ssh", - "user": "root", - "password": "supersecret", - }, - }, - }, - } - - // Checksum before the write - chksum, err := hashstructure.Hash(state, nil) - if err != nil { - t.Fatalf("hash: %s", err) - } - - buf := new(bytes.Buffer) - if err := testWriteStateV0(state, buf); err != nil { - t.Fatalf("err: %s", err) - } - - // Checksum after the write - chksumAfter, err := hashstructure.Hash(state, nil) - if err != nil { - t.Fatalf("hash: %s", err) - } - - if chksumAfter != chksum { - t.Fatalf("structure changed during serialization!") - } - - actual, err := ReadStateV0(buf) - if err != nil { - t.Fatalf("err: %s", err) - } - - // ReadState should not restore sensitive information! - state.Resources["foo"].ConnInfo = nil - - if !reflect.DeepEqual(actual, state) { - t.Fatalf("bad: %#v", actual) - } -} - -// sensitiveState is used to store sensitive state information -// that should not be serialized. This is only used temporarily -// and is restored into the state. -type sensitiveState struct { - ConnInfo map[string]map[string]string - - once sync.Once -} - -func (s *sensitiveState) init() { - s.once.Do(func() { - s.ConnInfo = make(map[string]map[string]string) - }) -} - -// testWriteStateV0 writes a state somewhere in a binary format. -// Only for testing now -func testWriteStateV0(d *StateV0, dst io.Writer) error { - // Write the magic bytes so we can determine the file format later - n, err := dst.Write([]byte(stateFormatMagic)) - if err != nil { - return err - } - if n != len(stateFormatMagic) { - return errors.New("failed to write state format magic bytes") - } - - // Write a version byte so we can iterate on version at some point - n, err = dst.Write([]byte{stateFormatVersion}) - if err != nil { - return err - } - if n != 1 { - return errors.New("failed to write state version byte") - } - - // Prevent sensitive information from being serialized - sensitive := &sensitiveState{} - sensitive.init() - for name, r := range d.Resources { - if r.ConnInfo != nil { - sensitive.ConnInfo[name] = r.ConnInfo - r.ConnInfo = nil - } - } - - // Serialize the state - err = gob.NewEncoder(dst).Encode(d) - - // Restore the state - for name, info := range sensitive.ConnInfo { - d.Resources[name].ConnInfo = info - } - - return err -}