"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 (
// 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
//
// 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()
// 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
}
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
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 {
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
}
// 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")
}
}
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
}
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 (
// 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)
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 {
}
v := version.Must(version.NewVersion(s.TFVersion))
- return SemVersion.LessThan(v)
+ return tfversion.SemVer.LessThan(v)
}
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()
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)
// 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.
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
// 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.
// 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.
}
for k, v := range m.Outputs {
- if v.Value == config.UnknownVariableValue {
+ if v.Value == hcl2shim.UnknownVariableValue {
delete(m.Outputs, k)
}
}
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 {
}
}
+// 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()
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()
// 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
}
}
continue
}
if diff.NewComputed {
- result.Attributes[k] = config.UnknownVariableValue
+ result.Attributes[k] = hcl2shim.UnknownVariableValue
continue
}
}
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))
// 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 {
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
// 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
// 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