]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blobdiff - vendor/github.com/hashicorp/terraform/terraform/state.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / state.go
index 074b68245466ada2fc214857e9e0341594dbf39c..6280fb0e4c057e4aa23cf168d8a48e3ad07b4f5e 100644 (file)
@@ -9,17 +9,31 @@ import (
        "io"
        "io/ioutil"
        "log"
+       "os"
        "reflect"
        "sort"
        "strconv"
        "strings"
        "sync"
 
-       "github.com/hashicorp/go-multierror"
-       "github.com/hashicorp/go-version"
-       "github.com/hashicorp/terraform/config"
+       "github.com/hashicorp/errwrap"
+       multierror "github.com/hashicorp/go-multierror"
+       uuid "github.com/hashicorp/go-uuid"
+       version "github.com/hashicorp/go-version"
+       "github.com/hashicorp/hcl2/hcl"
+       "github.com/hashicorp/hcl2/hcl/hclsyntax"
        "github.com/mitchellh/copystructure"
-       "github.com/satori/go.uuid"
+       "github.com/zclconf/go-cty/cty"
+       ctyjson "github.com/zclconf/go-cty/cty/json"
+
+       "github.com/hashicorp/terraform/addrs"
+       "github.com/hashicorp/terraform/config"
+       "github.com/hashicorp/terraform/config/hcl2shim"
+       "github.com/hashicorp/terraform/configs"
+       "github.com/hashicorp/terraform/configs/configschema"
+       "github.com/hashicorp/terraform/plans"
+       "github.com/hashicorp/terraform/tfdiags"
+       tfversion "github.com/hashicorp/terraform/version"
 )
 
 const (
@@ -30,26 +44,38 @@ const (
 // rootModulePath is the path of the root module
 var rootModulePath = []string{"root"}
 
+// normalizeModulePath transforms a legacy module path (which may or may not
+// have a redundant "root" label at the start of it) into an
+// addrs.ModuleInstance representing the same module.
+//
+// For legacy reasons, different parts of Terraform disagree about whether the
+// root module has the path []string{} or []string{"root"}, and so this
+// function accepts both and trims off the "root". An implication of this is
+// that it's not possible to actually have a module call in the root module
+// that is itself named "root", since that would be ambiguous.
+//
 // normalizeModulePath takes a raw module path and returns a path that
 // has the rootModulePath prepended to it. If I could go back in time I
 // would've never had a rootModulePath (empty path would be root). We can
 // still fix this but thats a big refactor that my branch doesn't make sense
 // for. Instead, this function normalizes paths.
-func normalizeModulePath(p []string) []string {
-       k := len(rootModulePath)
+func normalizeModulePath(p []string) addrs.ModuleInstance {
+       // FIXME: Remove this once everyone is using addrs.ModuleInstance.
 
-       // If we already have a root module prefix, we're done
-       if len(p) >= len(rootModulePath) {
-               if reflect.DeepEqual(p[:k], rootModulePath) {
-                       return p
-               }
+       if len(p) > 0 && p[0] == "root" {
+               p = p[1:]
        }
 
-       // None? Prefix it
-       result := make([]string, len(rootModulePath)+len(p))
-       copy(result, rootModulePath)
-       copy(result[k:], p)
-       return result
+       ret := make(addrs.ModuleInstance, len(p))
+       for i, name := range p {
+               // For now we don't actually support modules with multiple instances
+               // identified by keys, so we just treat every path element as a
+               // step with no key.
+               ret[i] = addrs.ModuleInstanceStep{
+                       Name: name,
+               }
+       }
+       return ret
 }
 
 // State keeps track of a snapshot state-of-the-world that Terraform
@@ -135,21 +161,43 @@ func (s *State) children(path []string) []*ModuleState {
 //
 // This should be the preferred method to add module states since it
 // allows us to optimize lookups later as well as control sorting.
-func (s *State) AddModule(path []string) *ModuleState {
+func (s *State) AddModule(path addrs.ModuleInstance) *ModuleState {
        s.Lock()
        defer s.Unlock()
 
        return s.addModule(path)
 }
 
-func (s *State) addModule(path []string) *ModuleState {
+func (s *State) addModule(path addrs.ModuleInstance) *ModuleState {
        // check if the module exists first
        m := s.moduleByPath(path)
        if m != nil {
                return m
        }
 
-       m = &ModuleState{Path: path}
+       // Lower the new-style address into a legacy-style address.
+       // This requires that none of the steps have instance keys, which is
+       // true for all addresses at the time of implementing this because
+       // "count" and "for_each" are not yet implemented for modules.
+       // For the purposes of state, the legacy address format also includes
+       // a redundant extra prefix element "root". It is important to include
+       // this because the "prune" method will remove any module that has a
+       // path length less than one, and other parts of the state code will
+       // trim off the first element indiscriminately.
+       legacyPath := make([]string, len(path)+1)
+       legacyPath[0] = "root"
+       for i, step := range path {
+               if step.InstanceKey != addrs.NoKey {
+                       // FIXME: Once the rest of Terraform is ready to use count and
+                       // for_each, remove all of this and just write the addrs.ModuleInstance
+                       // value itself into the ModuleState.
+                       panic("state cannot represent modules with count or for_each keys")
+               }
+
+               legacyPath[i+1] = step.Name
+       }
+
+       m = &ModuleState{Path: legacyPath}
        m.init()
        s.Modules = append(s.Modules, m)
        s.sort()
@@ -159,7 +207,7 @@ func (s *State) addModule(path []string) *ModuleState {
 // ModuleByPath is used to lookup the module state for the given path.
 // This should be the preferred lookup mechanism as it allows for future
 // lookup optimizations.
-func (s *State) ModuleByPath(path []string) *ModuleState {
+func (s *State) ModuleByPath(path addrs.ModuleInstance) *ModuleState {
        if s == nil {
                return nil
        }
@@ -169,7 +217,7 @@ func (s *State) ModuleByPath(path []string) *ModuleState {
        return s.moduleByPath(path)
 }
 
-func (s *State) moduleByPath(path []string) *ModuleState {
+func (s *State) moduleByPath(path addrs.ModuleInstance) *ModuleState {
        for _, mod := range s.Modules {
                if mod == nil {
                        continue
@@ -177,97 +225,14 @@ func (s *State) moduleByPath(path []string) *ModuleState {
                if mod.Path == nil {
                        panic("missing module path")
                }
-               if reflect.DeepEqual(mod.Path, path) {
+               modPath := normalizeModulePath(mod.Path)
+               if modPath.String() == path.String() {
                        return mod
                }
        }
        return nil
 }
 
-// ModuleOrphans returns all the module orphans in this state by
-// returning their full paths. These paths can be used with ModuleByPath
-// to return the actual state.
-func (s *State) ModuleOrphans(path []string, c *config.Config) [][]string {
-       s.Lock()
-       defer s.Unlock()
-
-       return s.moduleOrphans(path, c)
-
-}
-
-func (s *State) moduleOrphans(path []string, c *config.Config) [][]string {
-       // direct keeps track of what direct children we have both in our config
-       // and in our state. childrenKeys keeps track of what isn't an orphan.
-       direct := make(map[string]struct{})
-       childrenKeys := make(map[string]struct{})
-       if c != nil {
-               for _, m := range c.Modules {
-                       childrenKeys[m.Name] = struct{}{}
-                       direct[m.Name] = struct{}{}
-               }
-       }
-
-       // Go over the direct children and find any that aren't in our keys.
-       var orphans [][]string
-       for _, m := range s.children(path) {
-               key := m.Path[len(m.Path)-1]
-
-               // Record that we found this key as a direct child. We use this
-               // later to find orphan nested modules.
-               direct[key] = struct{}{}
-
-               // If we have a direct child still in our config, it is not an orphan
-               if _, ok := childrenKeys[key]; ok {
-                       continue
-               }
-
-               orphans = append(orphans, m.Path)
-       }
-
-       // Find the orphans that are nested...
-       for _, m := range s.Modules {
-               if m == nil {
-                       continue
-               }
-
-               // We only want modules that are at least grandchildren
-               if len(m.Path) < len(path)+2 {
-                       continue
-               }
-
-               // If it isn't part of our tree, continue
-               if !reflect.DeepEqual(path, m.Path[:len(path)]) {
-                       continue
-               }
-
-               // If we have the direct child, then just skip it.
-               key := m.Path[len(path)]
-               if _, ok := direct[key]; ok {
-                       continue
-               }
-
-               orphanPath := m.Path[:len(path)+1]
-
-               // Don't double-add if we've already added this orphan (which can happen if
-               // there are multiple nested sub-modules that get orphaned together).
-               alreadyAdded := false
-               for _, o := range orphans {
-                       if reflect.DeepEqual(o, orphanPath) {
-                               alreadyAdded = true
-                               break
-                       }
-               }
-               if alreadyAdded {
-                       continue
-               }
-
-               // Add this orphan
-               orphans = append(orphans, orphanPath)
-       }
-
-       return orphans
-}
-
 // Empty returns true if the state is empty.
 func (s *State) Empty() bool {
        if s == nil {
@@ -440,7 +405,7 @@ func (s *State) removeModule(path []string, v *ModuleState) {
 
 func (s *State) removeResource(path []string, v *ResourceState) {
        // Get the module this resource lives in. If it doesn't exist, we're done.
-       mod := s.moduleByPath(path)
+       mod := s.moduleByPath(normalizeModulePath(path))
        if mod == nil {
                return
        }
@@ -484,7 +449,7 @@ func (s *State) removeInstance(path []string, r *ResourceState, v *InstanceState
 
 // RootModule returns the ModuleState for the root module
 func (s *State) RootModule() *ModuleState {
-       root := s.ModuleByPath(rootModulePath)
+       root := s.ModuleByPath(addrs.RootModuleInstance)
        if root == nil {
                panic("missing root module")
        }
@@ -519,7 +484,7 @@ func (s *State) equal(other *State) bool {
        }
        for _, m := range s.Modules {
                // This isn't very optimal currently but works.
-               otherM := other.moduleByPath(m.Path)
+               otherM := other.moduleByPath(normalizeModulePath(m.Path))
                if otherM == nil {
                        return false
                }
@@ -533,6 +498,43 @@ func (s *State) equal(other *State) bool {
        return true
 }
 
+// MarshalEqual is similar to Equal but provides a stronger definition of
+// "equal", where two states are equal if and only if their serialized form
+// is byte-for-byte identical.
+//
+// This is primarily useful for callers that are trying to save snapshots
+// of state to persistent storage, allowing them to detect when a new
+// snapshot must be taken.
+//
+// Note that the serial number and lineage are included in the serialized form,
+// so it's the caller's responsibility to properly manage these attributes
+// so that this method is only called on two states that have the same
+// serial and lineage, unless detecting such differences is desired.
+func (s *State) MarshalEqual(other *State) bool {
+       if s == nil && other == nil {
+               return true
+       } else if s == nil || other == nil {
+               return false
+       }
+
+       recvBuf := &bytes.Buffer{}
+       otherBuf := &bytes.Buffer{}
+
+       err := WriteState(s, recvBuf)
+       if err != nil {
+               // should never happen, since we're writing to a buffer
+               panic(err)
+       }
+
+       err = WriteState(other, otherBuf)
+       if err != nil {
+               // should never happen, since we're writing to a buffer
+               panic(err)
+       }
+
+       return bytes.Equal(recvBuf.Bytes(), otherBuf.Bytes())
+}
+
 type StateAgeComparison int
 
 const (
@@ -603,6 +605,10 @@ func (s *State) SameLineage(other *State) bool {
 // DeepCopy performs a deep copy of the state structure and returns
 // a new structure.
 func (s *State) DeepCopy() *State {
+       if s == nil {
+               return nil
+       }
+
        copy, err := copystructure.Config{Lock: true}.Copy(s)
        if err != nil {
                panic(err)
@@ -611,30 +617,6 @@ func (s *State) DeepCopy() *State {
        return copy.(*State)
 }
 
-// IncrementSerialMaybe increments the serial number of this state
-// if it different from the other state.
-func (s *State) IncrementSerialMaybe(other *State) {
-       if s == nil {
-               return
-       }
-       if other == nil {
-               return
-       }
-       s.Lock()
-       defer s.Unlock()
-
-       if s.Serial > other.Serial {
-               return
-       }
-       if other.TFVersion != s.TFVersion || !s.equal(other) {
-               if other.Serial > s.Serial {
-                       s.Serial = other.Serial
-               }
-
-               s.Serial++
-       }
-}
-
 // FromFutureTerraform checks if this state was written by a Terraform
 // version from the future.
 func (s *State) FromFutureTerraform() bool {
@@ -647,7 +629,7 @@ func (s *State) FromFutureTerraform() bool {
        }
 
        v := version.Must(version.NewVersion(s.TFVersion))
-       return SemVersion.LessThan(v)
+       return tfversion.SemVer.LessThan(v)
 }
 
 func (s *State) Init() {
@@ -660,8 +642,9 @@ func (s *State) init() {
        if s.Version == 0 {
                s.Version = StateVersion
        }
-       if s.moduleByPath(rootModulePath) == nil {
-               s.addModule(rootModulePath)
+
+       if s.moduleByPath(addrs.RootModuleInstance) == nil {
+               s.addModule(addrs.RootModuleInstance)
        }
        s.ensureHasLineage()
 
@@ -686,7 +669,11 @@ func (s *State) EnsureHasLineage() {
 
 func (s *State) ensureHasLineage() {
        if s.Lineage == "" {
-               s.Lineage = uuid.NewV4().String()
+               lineage, err := uuid.GenerateUUID()
+               if err != nil {
+                       panic(fmt.Errorf("Failed to generate lineage: %v", err))
+               }
+               s.Lineage = lineage
                log.Printf("[DEBUG] New state was assigned lineage %q\n", s.Lineage)
        } else {
                log.Printf("[TRACE] Preserving existing state lineage %q\n", s.Lineage)
@@ -786,13 +773,9 @@ func (s *State) String() string {
 
 // BackendState stores the configuration to connect to a remote backend.
 type BackendState struct {
-       Type   string                 `json:"type"`   // Backend type
-       Config map[string]interface{} `json:"config"` // Backend raw config
-
-       // Hash is the hash code to uniquely identify the original source
-       // configuration. We use this to detect when there is a change in
-       // configuration even when "type" isn't changed.
-       Hash uint64 `json:"hash"`
+       Type      string          `json:"type"`   // Backend type
+       ConfigRaw json.RawMessage `json:"config"` // Backend raw config
+       Hash      uint64          `json:"hash"`   // Hash of portion of configuration from config files
 }
 
 // Empty returns true if BackendState has no state.
@@ -800,25 +783,50 @@ func (s *BackendState) Empty() bool {
        return s == nil || s.Type == ""
 }
 
-// Rehash returns a unique content hash for this backend's configuration
-// as a uint64 value.
-// The Hash stored in the backend state needs to match the config itself, but
-// we need to compare the backend config after it has been combined with all
-// options.
-// This function must match the implementation used by config.Backend.
-func (s *BackendState) Rehash() uint64 {
+// Config decodes the type-specific configuration object using the provided
+// schema and returns the result as a cty.Value.
+//
+// An error is returned if the stored configuration does not conform to the
+// given schema.
+func (s *BackendState) Config(schema *configschema.Block) (cty.Value, error) {
+       ty := schema.ImpliedType()
        if s == nil {
-               return 0
+               return cty.NullVal(ty), nil
        }
+       return ctyjson.Unmarshal(s.ConfigRaw, ty)
+}
 
-       cfg := config.Backend{
-               Type: s.Type,
-               RawConfig: &config.RawConfig{
-                       Raw: s.Config,
-               },
+// SetConfig replaces (in-place) the type-specific configuration object using
+// the provided value and associated schema.
+//
+// An error is returned if the given value does not conform to the implied
+// type of the schema.
+func (s *BackendState) SetConfig(val cty.Value, schema *configschema.Block) error {
+       ty := schema.ImpliedType()
+       buf, err := ctyjson.Marshal(val, ty)
+       if err != nil {
+               return err
+       }
+       s.ConfigRaw = buf
+       return nil
+}
+
+// ForPlan produces an alternative representation of the reciever that is
+// suitable for storing in a plan. The current workspace must additionally
+// be provided, to be stored alongside the backend configuration.
+//
+// The backend configuration schema is required in order to properly
+// encode the backend-specific configuration settings.
+func (s *BackendState) ForPlan(schema *configschema.Block, workspaceName string) (*plans.Backend, error) {
+       if s == nil {
+               return nil, nil
        }
 
-       return cfg.Rehash()
+       configVal, err := s.Config(schema)
+       if err != nil {
+               return nil, errwrap.Wrapf("failed to decode backend config: {{err}}", err)
+       }
+       return plans.NewBackend(s.Type, configVal, schema, workspaceName)
 }
 
 // RemoteState is used to track the information about a remote
@@ -959,6 +967,10 @@ type ModuleState struct {
        // always disjoint, so the path represents amodule tree
        Path []string `json:"path"`
 
+       // Locals are kept only transiently in-memory, because we can always
+       // re-compute them.
+       Locals map[string]interface{} `json:"-"`
+
        // Outputs declared by the module and maintained for each module
        // even though only the root module technically needs to be kept.
        // This allows operators to inspect values at the boundaries.
@@ -1060,33 +1072,64 @@ func (m *ModuleState) IsDescendent(other *ModuleState) bool {
 // 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 (m *ModuleState) Orphans(c *config.Config) []string {
+func (m *ModuleState) Orphans(c *configs.Module) []addrs.ResourceInstance {
        m.Lock()
        defer m.Unlock()
 
-       keys := make(map[string]struct{})
-       for k, _ := range m.Resources {
-               keys[k] = struct{}{}
+       inConfig := make(map[string]struct{})
+       if c != nil {
+               for _, r := range c.ManagedResources {
+                       inConfig[r.Addr().String()] = struct{}{}
+               }
+               for _, r := range c.DataResources {
+                       inConfig[r.Addr().String()] = struct{}{}
+               }
        }
 
-       if c != nil {
-               for _, r := range c.Resources {
-                       delete(keys, r.Id())
+       var result []addrs.ResourceInstance
+       for k := range m.Resources {
+               // Since we've not yet updated state to use our new address format,
+               // we need to do some shimming here.
+               legacyAddr, err := parseResourceAddressInternal(k)
+               if err != nil {
+                       // Suggests that the user tampered with the state, since we always
+                       // generate valid internal addresses.
+                       log.Printf("ModuleState has invalid resource key %q. Ignoring.", k)
+                       continue
+               }
 
-                       for k, _ := range keys {
-                               if strings.HasPrefix(k, r.Id()+".") {
-                                       delete(keys, k)
-                               }
-                       }
+               addr := legacyAddr.AbsResourceInstanceAddr().Resource
+               compareKey := addr.Resource.String() // compare by resource address, ignoring instance key
+               if _, exists := inConfig[compareKey]; !exists {
+                       result = append(result, addr)
                }
        }
+       return result
+}
 
-       result := make([]string, 0, len(keys))
-       for k, _ := range keys {
-               result = append(result, k)
+// RemovedOutputs returns a list of outputs that are in the State but aren't
+// present in the configuration itself.
+func (s *ModuleState) RemovedOutputs(outputs map[string]*configs.Output) []addrs.OutputValue {
+       if outputs == nil {
+               // If we got no output map at all then we'll just treat our set of
+               // configured outputs as empty, since that suggests that they've all
+               // been removed by removing their containing module.
+               outputs = make(map[string]*configs.Output)
        }
 
-       return result
+       s.Lock()
+       defer s.Unlock()
+
+       var ret []addrs.OutputValue
+       for n := range s.Outputs {
+               if _, declared := outputs[n]; !declared {
+                       ret = append(ret, addrs.OutputValue{
+                               Name: n,
+                       })
+               }
+       }
+
+       return ret
 }
 
 // View returns a view with the given resource prefix.
@@ -1158,7 +1201,7 @@ func (m *ModuleState) prune() {
        }
 
        for k, v := range m.Outputs {
-               if v.Value == config.UnknownVariableValue {
+               if v.Value == hcl2shim.UnknownVariableValue {
                        delete(m.Outputs, k)
                }
        }
@@ -1290,6 +1333,10 @@ func (m *ModuleState) String() string {
        return buf.String()
 }
 
+func (m *ModuleState) Empty() bool {
+       return len(m.Locals) == 0 && len(m.Outputs) == 0 && len(m.Resources) == 0
+}
+
 // ResourceStateKey is a structured representation of the key used for the
 // ModuleState.Resources mapping
 type ResourceStateKey struct {
@@ -1485,6 +1532,24 @@ func (s *ResourceState) Untaint() {
        }
 }
 
+// ProviderAddr returns the provider address for the receiver, by parsing the
+// string representation saved in state. An error can be returned if the
+// value in state is corrupt.
+func (s *ResourceState) ProviderAddr() (addrs.AbsProviderConfig, error) {
+       var diags tfdiags.Diagnostics
+
+       str := s.Provider
+       traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
+       diags = diags.Append(travDiags)
+       if travDiags.HasErrors() {
+               return addrs.AbsProviderConfig{}, diags.Err()
+       }
+
+       addr, addrDiags := addrs.ParseAbsProviderConfig(traversal)
+       diags = diags.Append(addrDiags)
+       return addr, diags.Err()
+}
+
 func (s *ResourceState) init() {
        s.Lock()
        defer s.Unlock()
@@ -1593,6 +1658,51 @@ func (s *InstanceState) init() {
        s.Ephemeral.init()
 }
 
+// NewInstanceStateShimmedFromValue is a shim method to lower a new-style
+// object value representing the attributes of an instance object into the
+// legacy InstanceState representation.
+//
+// This is for shimming to old components only and should not be used in new code.
+func NewInstanceStateShimmedFromValue(state cty.Value, schemaVersion int) *InstanceState {
+       attrs := hcl2shim.FlatmapValueFromHCL2(state)
+       return &InstanceState{
+               ID:         attrs["id"],
+               Attributes: attrs,
+               Meta: map[string]interface{}{
+                       "schema_version": schemaVersion,
+               },
+       }
+}
+
+// AttrsAsObjectValue shims from the legacy InstanceState representation to
+// a new-style cty object value representation of the state attributes, using
+// the given type for guidance.
+//
+// The given type must be the implied type of the schema of the resource type
+// of the object whose state is being converted, or the result is undefined.
+//
+// This is for shimming from old components only and should not be used in
+// new code.
+func (s *InstanceState) AttrsAsObjectValue(ty cty.Type) (cty.Value, error) {
+       if s == nil {
+               // if the state is nil, we need to construct a complete cty.Value with
+               // null attributes, rather than a single cty.NullVal(ty)
+               s = &InstanceState{}
+       }
+
+       if s.Attributes == nil {
+               s.Attributes = map[string]string{}
+       }
+
+       // make sure ID is included in the attributes. The InstanceState.ID value
+       // takes precedence.
+       if s.ID != "" {
+               s.Attributes["id"] = s.ID
+       }
+
+       return hcl2shim.HCL2ValueFromFlatmap(s.Attributes, ty)
+}
+
 // Copy all the Fields from another InstanceState
 func (s *InstanceState) Set(from *InstanceState) {
        s.Lock()
@@ -1663,7 +1773,20 @@ func (s *InstanceState) Equal(other *InstanceState) bool {
                // We only do the deep check if both are non-nil. If one is nil
                // we treat it as equal since their lengths are both zero (check
                // above).
-               if !reflect.DeepEqual(s.Meta, other.Meta) {
+               //
+               // Since this can contain numeric values that may change types during
+               // serialization, let's compare the serialized values.
+               sMeta, err := json.Marshal(s.Meta)
+               if err != nil {
+                       // marshaling primitives shouldn't ever error out
+                       panic(err)
+               }
+               otherMeta, err := json.Marshal(other.Meta)
+               if err != nil {
+                       panic(err)
+               }
+
+               if !bytes.Equal(sMeta, otherMeta) {
                        return false
                }
        }
@@ -1704,7 +1827,7 @@ func (s *InstanceState) MergeDiff(d *InstanceDiff) *InstanceState {
                                continue
                        }
                        if diff.NewComputed {
-                               result.Attributes[k] = config.UnknownVariableValue
+                               result.Attributes[k] = hcl2shim.UnknownVariableValue
                                continue
                        }
 
@@ -1716,13 +1839,19 @@ func (s *InstanceState) MergeDiff(d *InstanceDiff) *InstanceState {
 }
 
 func (s *InstanceState) String() string {
+       notCreated := "<not created>"
+
+       if s == nil {
+               return notCreated
+       }
+
        s.Lock()
        defer s.Unlock()
 
        var buf bytes.Buffer
 
-       if s == nil || s.ID == "" {
-               return "<not created>"
+       if s.ID == "" {
+               return notCreated
        }
 
        buf.WriteString(fmt.Sprintf("ID = %s\n", s.ID))
@@ -1806,11 +1935,19 @@ var ErrNoState = errors.New("no state")
 // ReadState reads a state structure out of a reader in the format that
 // was written by WriteState.
 func ReadState(src io.Reader) (*State, error) {
+       // check for a nil file specifically, since that produces a platform
+       // specific error if we try to use it in a bufio.Reader.
+       if f, ok := src.(*os.File); ok && f == nil {
+               return nil, ErrNoState
+       }
+
        buf := bufio.NewReader(src)
+
        if _, err := buf.Peek(1); err != nil {
-               // the error is either io.EOF or "invalid argument", and both are from
-               // an empty state.
-               return nil, ErrNoState
+               if err == io.EOF {
+                       return nil, ErrNoState
+               }
+               return nil, err
        }
 
        if err := testForV0State(buf); err != nil {
@@ -1873,7 +2010,7 @@ func ReadState(src io.Reader) (*State, error) {
                result = v3State
        default:
                return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.",
-                       SemVersion.String(), versionIdentifier.Version)
+                       tfversion.SemVer.String(), versionIdentifier.Version)
        }
 
        // If we reached this place we must have a result set
@@ -1917,7 +2054,7 @@ func ReadStateV2(jsonBytes []byte) (*State, error) {
        // version that we don't understand
        if state.Version > StateVersion {
                return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.",
-                       SemVersion.String(), state.Version)
+                       tfversion.SemVer.String(), state.Version)
        }
 
        // Make sure the version is semantic
@@ -1952,7 +2089,7 @@ func ReadStateV3(jsonBytes []byte) (*State, error) {
        // version that we don't understand
        if state.Version > StateVersion {
                return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.",
-                       SemVersion.String(), state.Version)
+                       tfversion.SemVer.String(), state.Version)
        }
 
        // Make sure the version is semantic