diff --git a/terraform/context.go b/terraform/context.go index 3572508c0..da4d707cd 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -505,6 +505,9 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { return nil } + // Ensure the state is initialized + r.State.init() + if !diff.Destroy { // Since we need the configuration, interpolate the variables if err := r.Config.interpolate(c); err != nil { @@ -543,11 +546,6 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { } } - // If we do not have any connection info, initialize - if r.State.Primary.Ephemeral.ConnInfo == nil { - r.State.Primary.Ephemeral.init() - } - // Remove any output values from the diff for k, ad := range diff.Attributes { if ad.Type == DiffAttrOutput { @@ -571,42 +569,36 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { // Make sure the result is instantiated if rs == nil { rs = new(ResourceState) + rs.init() } // Force the resource state type to be our type rs.Type = r.State.Type // Force the "id" attribute to be our ID - if rs.ID != "" { - if rs.Attributes == nil { - rs.Attributes = make(map[string]string) - } - - rs.Attributes["id"] = rs.ID + if rs.Primary.ID != "" { + rs.Primary.Attributes["id"] = rs.Primary.ID } - for ak, av := range rs.Attributes { + for ak, av := range rs.Primary.Attributes { // If the value is the unknown variable value, then it is an error. // In this case we record the error and remove it from the state if av == config.UnknownVariableValue { errs = append(errs, fmt.Errorf( "Attribute with unknown value: %s", ak)) - delete(rs.Attributes, ak) + delete(rs.Primary.Attributes, ak) } } // Update the resulting diff c.sl.Lock() - if rs.ID == "" { - delete(c.state.Resources, r.Id) - delete(c.state.Tainted, r.Id) - } else { - c.state.Resources[r.Id] = rs - // We always mark the resource as tainted here in case a - // hook below during provisioning does HookActionStop. This - // way, we keep the resource tainted. - c.state.Tainted[r.Id] = struct{}{} + // TODO: Get other modules + mod := c.state.RootModule() + if rs.Primary.ID == "" && len(rs.Tainted) == 0 { + delete(mod.Resources, r.Id) + } else { + mod.Resources[r.Id] = rs } c.sl.Unlock() @@ -617,7 +609,7 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { // Additionally, we need to be careful to not run this if there // was an error during the provider apply. tainted := false - if applyerr == nil && r.State.ID == "" && len(r.Provisioners) > 0 { + if applyerr == nil && r.State.Primary.ID == "" && len(r.Provisioners) > 0 { for _, h := range c.hooks { handleHook(h.PreProvisionResource(r.Id, r.State)) } @@ -635,9 +627,8 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { c.sl.Lock() if tainted { log.Printf("[DEBUG] %s: Marking as tainted", r.Id) - c.state.Tainted[r.Id] = struct{}{} - } else { - delete(c.state.Tainted, r.Id) + rs.Tainted = append(rs.Tainted, rs.Primary) + rs.Primary = nil } c.sl.Unlock() @@ -665,9 +656,9 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { // defined after the resource creation has already completed. func (c *Context) applyProvisioners(r *Resource, rs *ResourceState) error { // Store the original connection info, restore later - origConnInfo := rs.ConnInfo + origConnInfo := rs.Primary.Ephemeral.ConnInfo defer func() { - rs.ConnInfo = origConnInfo + rs.Primary.Ephemeral.ConnInfo = origConnInfo }() for _, prov := range r.Provisioners { @@ -710,7 +701,7 @@ func (c *Context) applyProvisioners(r *Resource, rs *ResourceState) error { overlay[k] = fmt.Sprintf("%v", vt) } } - rs.ConnInfo = overlay + rs.Primary.Ephemeral.ConnInfo = overlay // Invoke the Provisioner for _, h := range c.hooks { @@ -778,16 +769,16 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc { diff.Destroy = true } - if diff.RequiresNew() && r.State.ID != "" { + if diff.RequiresNew() && r.State.Primary.ID != "" { // This will also require a destroy diff.Destroy = true } - if diff.RequiresNew() || r.State.ID == "" { + if diff.RequiresNew() || r.State.Primary.ID == "" { // Add diff to compute new ID diff.init() diff.Attributes["id"] = &ResourceAttrDiff{ - Old: r.State.Attributes["id"], + Old: r.State.Primary.Attributes["id"], NewComputed: true, RequiresNew: true, Type: DiffAttrOutput, @@ -812,7 +803,10 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc { // Update our internal state so that variable computation works c.sl.Lock() defer c.sl.Unlock() - c.state.Resources[r.Id] = r.State + + // TODO: Handle other modules + mod := c.state.RootModule() + mod.Resources[r.Id] = r.State return nil } @@ -833,7 +827,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc { } r := rn.Resource - if r.State.ID != "" { + if r.State.Primary.ID != "" { log.Printf("[DEBUG] %s: Making for destroy", r.Id) l.Lock() @@ -849,7 +843,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc { func (c *Context) refreshWalkFn() depgraph.WalkFunc { cb := func(r *Resource) error { - if r.State.ID == "" { + if r.State.Primary.ID == "" { log.Printf("[DEBUG] %s: Not refreshing, ID is empty", r.Id) return nil } @@ -870,10 +864,13 @@ func (c *Context) refreshWalkFn() depgraph.WalkFunc { rs.Type = r.State.Type c.sl.Lock() - if rs.ID == "" { - delete(c.state.Resources, r.Id) + + // TODO: Handle other moduels + mod := c.state.RootModule() + if rs.Primary.ID == "" { + delete(mod.Resources, r.Id) } else { - c.state.Resources[r.Id] = rs + mod.Resources[r.Id] = rs } c.sl.Unlock() diff --git a/terraform/state.go b/terraform/state.go index ed77c2b71..a00aa4737 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "io" + + "github.com/hashicorp/terraform/config" ) // rootModulePath is the path of the root module @@ -82,6 +84,15 @@ type ModuleState struct { Resources map[string]*ResourceState `json:"resources"` } +func (m *ModuleState) init() { + if m.Outputs == nil { + m.Outputs = make(map[string]stirng) + } + if m.Resources == nil { + m.Resources = make(map[string]*ResourceState) + } +} + func (m *ModuleState) deepcopy() *ModuleState { n := &ModuleState{ Path: make([]string, len(m.Path)), @@ -154,6 +165,13 @@ type ResourceState struct { Tainted []*InstanceState `json:"tainted,omitempty"` } +func (r *ResourceState) init() { + if i.Primary == nil { + i.Primary = &InstanceState{} + i.Primary.init() + } +} + func (r *ResourceState) deepcopy() *ResourceState { n := &ResourceState{ Type: r.Type, @@ -181,6 +199,44 @@ func (r *ResourceState) prune() { r.Instances = r.Instances[:n] } +// 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 *ResourceState) MergeDiff(d *ResourceDiff) *ResourceState { + var result ResourceState + if s != nil { + result = *s + } + result.init() + + if s != nil { + for k, v := range s.Primary.Attributes { + result.Primary.Attributes[k] = v + } + } + if d != nil { + for k, diff := range d.Attributes { + if diff.NewRemoved { + delete(result.Primary.Attributes, k) + continue + } + if diff.NewComputed { + result.Primary.Attributes[k] = config.UnknownVariableValue + continue + } + + result.Primary.Attributes[k] = diff.New + } + } + + return &result +} + // InstanceState is used to track the unique state information belonging // to a given instance. type InstanceState struct { @@ -204,6 +260,13 @@ type InstanceState struct { Ephemeral EphemeralState `json:"-"` } +func (i *InstanceState) init() { + if i.Attributes == nil { + i.Attributes = make(map[string]string) + } + i.Ephemeral.init() +} + func (i *InstanceState) deepcopy() *InstanceState { n := &InstanceState{ ID: i.ID,