core: Re-implement EvalWriteDiff to work with new plan types

This commit is contained in:
Martin Atkins 2018-08-27 14:33:08 -07:00
parent 686018ae12
commit bd299b9a22
10 changed files with 116 additions and 41 deletions

View File

@ -55,6 +55,15 @@ func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key st
return nil return nil
} }
// SyncWrapper returns a wrapper object around the receiver that can be used
// to make certain changes to the receiver in a concurrency-safe way, as long
// as all callers share the same wrapper object.
func (c *Changes) SyncWrapper() *ChangesSync {
return &ChangesSync{
changes: c,
}
}
// ResourceInstanceChange describes a change to a particular resource instance // ResourceInstanceChange describes a change to a particular resource instance
// object. // object.
type ResourceInstanceChange struct { type ResourceInstanceChange struct {

View File

@ -66,6 +66,38 @@ func (rcs *ResourceInstanceChangeSrc) Decode(ty cty.Type) (*ResourceInstanceChan
}, nil }, nil
} }
// DeepCopy creates a copy of the receiver where any pointers to nested mutable
// values are also copied, thus ensuring that future mutations of the receiver
// will not affect the copy.
//
// Some types used within a resource change are immutable by convention even
// though the Go language allows them to be mutated, such as the types from
// the addrs package. These are _not_ copied by this method, under the
// assumption that callers will behave themselves.
func (rcs *ResourceInstanceChangeSrc) DeepCopy() *ResourceInstanceChangeSrc {
if rcs == nil {
return nil
}
ret := *rcs
if len(ret.RequiredReplace) != 0 {
rr := make([]cty.Path, len(ret.RequiredReplace))
copy(rr, ret.RequiredReplace)
ret.RequiredReplace = rr
}
if len(ret.Private) != 0 {
private := make([]byte, len(ret.Private))
copy(private, ret.Private)
ret.Private = private
}
ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy()
ret.ChangeSrc.After = ret.ChangeSrc.After.Copy()
return &ret
}
// OutputChange describes a change to an output value. // OutputChange describes a change to an output value.
type OutputChangeSrc struct { type OutputChangeSrc struct {
// ChangeSrc is an embedded description of the not-yet-decoded change. // ChangeSrc is an embedded description of the not-yet-decoded change.

View File

@ -16,3 +16,20 @@ type ChangesSync struct {
lock sync.Mutex lock sync.Mutex
changes *Changes changes *Changes
} }
// AppendResourceInstanceChange records the given resource instance change in
// the set of planned resource changes.
//
// The caller must ensure that there are no concurrent writes to the given
// change while this method is running, but it is safe to resume mutating
// it after this method returns without affecting the saved change.
func (cs *ChangesSync) AppendResourceInstanceChange(changeSrc *ResourceInstanceChangeSrc) {
if cs == nil {
panic("AppendResourceInstanceChange on nil ChangesSync")
}
cs.lock.Lock()
defer cs.lock.Unlock()
s := changeSrc.DeepCopy()
cs.changes.Resources = append(cs.changes.Resources, s)
}

View File

@ -83,3 +83,14 @@ func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) {
func (v DynamicValue) ImpliedType() (cty.Type, error) { func (v DynamicValue) ImpliedType() (cty.Type, error) {
return ctymsgpack.ImpliedType([]byte(v)) return ctymsgpack.ImpliedType([]byte(v))
} }
// Copy produces a copy of the receiver with a distinct backing array.
func (v DynamicValue) Copy() DynamicValue {
if len(v) == 0 {
return nil
}
ret := make(DynamicValue, len(v))
copy(ret, v)
return ret
}

View File

@ -718,6 +718,7 @@ func (c *Context) graphWalker(operation walkOperation) *ContextGraphWalker {
return &ContextGraphWalker{ return &ContextGraphWalker{
Context: c, Context: c,
State: c.state.SyncWrapper(), State: c.state.SyncWrapper(),
Changes: c.changes.SyncWrapper(),
Operation: operation, Operation: operation,
StopContext: c.runContext, StopContext: c.runContext,
RootVariableValues: c.variables, RootVariableValues: c.variables,

View File

@ -734,39 +734,38 @@ func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
type EvalWriteDiff struct { type EvalWriteDiff struct {
Addr addrs.ResourceInstance Addr addrs.ResourceInstance
DeposedKey states.DeposedKey DeposedKey states.DeposedKey
ProviderSchema **ProviderSchema
Change **plans.ResourceInstanceChange Change **plans.ResourceInstanceChange
} }
// TODO: test // TODO: test
func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
return nil, fmt.Errorf("EvalWriteDiff not yet updated for new plan types") providerSchema := *n.ProviderSchema
/* change := *n.Change
diff, lock := ctx.Diff()
// The diff to write, if its empty it should write nil if change.Addr.String() != n.Addr.String() || change.DeposedKey != n.DeposedKey {
var diffVal *InstanceDiff // Should never happen, and indicates a bug in the caller.
if n.Diff != nil { panic("inconsistent address and/or deposed key in EvalWriteDiff")
diffVal = *n.Diff
}
if diffVal.Empty() {
diffVal = nil
} }
// Acquire the lock so that we can do this safely concurrently schema := providerSchema.ResourceTypes[n.Addr.Resource.Type]
lock.Lock() if schema == nil {
defer lock.Unlock() // Should be caught during validation, so we don't bother with a pretty error here
return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
// Write the diff
modDiff := diff.ModuleByPath(ctx.Path())
if modDiff == nil {
modDiff = diff.AddModule(ctx.Path())
} }
if diffVal != nil {
modDiff.Resources[n.Name] = diffVal csrc, err := change.Encode(schema.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to encode planned changes for %s: %s", n.Addr, err)
}
changes := ctx.Changes()
changes.AppendResourceInstanceChange(csrc)
if n.DeposedKey == states.NotDeposed {
log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s", change.Action, n.Addr)
} else { } else {
delete(modDiff.Resources, n.Name) log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s deposed object %s", change.Action, n.Addr, n.DeposedKey)
} }
return nil, nil return nil, nil
*/
} }

View File

@ -182,6 +182,7 @@ func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
// later nodes won't see a diff that's now a no-op. // later nodes won't see a diff that's now a no-op.
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: nil, Change: nil,
}, },
@ -348,6 +349,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
// is no longer a diff! // is no longer a diff!
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: nil, Change: nil,
}, },

View File

@ -79,6 +79,7 @@ func (n *NodePlanDestroyableResourceInstance) EvalTree() EvalNode {
}, },
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change, Change: &change,
}, },
}, },

View File

@ -100,6 +100,7 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change, Change: &change,
}, },
}, },
@ -158,6 +159,7 @@ func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
}, },
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change, Change: &change,
}, },
}, },

View File

@ -69,6 +69,7 @@ func (n *NodePlannableResourceInstanceOrphan) EvalTree() EvalNode {
}, },
&EvalWriteDiff{ &EvalWriteDiff{
Addr: addr.Resource, Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change, Change: &change,
}, },
&EvalWriteState{ &EvalWriteState{