copy timouts into plan and apply state

helper/schema will remove "timeouts" from the config, and stash them in
the diff.Meta map. Terraform sees "timeouts" as a regular config block,
so needs them to be present in the state in order to not show a diff.

Have the GRPCProviderServer shim copy all timeout values into any state
it returns to provide consistent diffs in core.
This commit is contained in:
James Bardin 2018-10-30 12:59:45 -04:00
parent 121c9c127f
commit e38a5a769d
2 changed files with 49 additions and 10 deletions

View File

@ -412,20 +412,22 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
// helper/schema should always copy the ID over, but do it again just to be safe // helper/schema should always copy the ID over, but do it again just to be safe
newInstanceState.Attributes["id"] = newInstanceState.ID newInstanceState.Attributes["id"] = newInstanceState.ID
newConfigVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, block.ImpliedType()) newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
newConfigMP, err := msgpack.Marshal(newConfigVal, block.ImpliedType()) newStateVal = copyTimeoutValues(newStateVal, stateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
resp.NewState = &proto.DynamicValue{ resp.NewState = &proto.DynamicValue{
Msgpack: newConfigMP, Msgpack: newStateMP,
} }
return resp, nil return resp, nil
@ -461,6 +463,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
return resp, nil return resp, nil
} }
} }
priorState.Meta = priorPrivate priorState.Meta = priorPrivate
// turn the proposed state into a legacy configuration // turn the proposed state into a legacy configuration
@ -488,6 +491,8 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
return resp, nil return resp, nil
} }
plannedStateVal = copyTimeoutValues(plannedStateVal, proposedNewStateVal)
plannedMP, err := msgpack.Marshal(plannedStateVal, block.ImpliedType()) plannedMP, err := msgpack.Marshal(plannedStateVal, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
@ -498,12 +503,14 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
} }
// the Meta field gets encoded into PlannedPrivate // the Meta field gets encoded into PlannedPrivate
if diff.Meta != nil {
plannedPrivate, err := json.Marshal(diff.Meta) plannedPrivate, err := json.Marshal(diff.Meta)
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
resp.PlannedPrivate = plannedPrivate resp.PlannedPrivate = plannedPrivate
}
// collect the attributes that require instance replacement, and convert // collect the attributes that require instance replacement, and convert
// them to cty.Paths. // them to cty.Paths.
@ -594,7 +601,10 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
Meta: make(map[string]interface{}), Meta: make(map[string]interface{}),
} }
} }
if private != nil {
diff.Meta = private diff.Meta = private
}
newInstanceState, err := s.provider.Apply(info, priorState, diff) newInstanceState, err := s.provider.Apply(info, priorState, diff)
if err != nil { if err != nil {
@ -614,6 +624,8 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
} }
} }
newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
@ -726,6 +738,8 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
return resp, nil return resp, nil
} }
newStateVal = copyTimeoutValues(newStateVal, configVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
@ -770,3 +784,28 @@ func pathToAttributePath(path cty.Path) *proto.AttributePath {
return &proto.AttributePath{Steps: steps} return &proto.AttributePath{Steps: steps}
} }
// helper/schema throws away timeout values from the config and stores them in
// the Private/Meta fields. we need to copy those values into the planned state
// so that core doesn't see a perpetual diff with the timeout block.
func copyTimeoutValues(to cty.Value, from cty.Value) cty.Value {
// if `from` is null, then there are no attributes, and if `to` is null we
// are planning to remove it altogether.
if from.IsNull() || to.IsNull() {
return to
}
fromAttrs := from.AsValueMap()
timeouts, ok := fromAttrs[schema.TimeoutsConfigKey]
// no timeouts to copy
// timeouts shouldn't be unknown, but don't copy possibly invalid values
if !ok || timeouts.IsNull() || !timeouts.IsWhollyKnown() {
return to
}
toAttrs := to.AsValueMap()
toAttrs[schema.TimeoutsConfigKey] = timeouts
return cty.ObjectVal(toAttrs)
}

View File

@ -77,7 +77,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep,
} }
// We need to keep a copy of the state prior to destroying // We need to keep a copy of the state prior to destroying
// such that destroy steps can verify their behaviour in the check // such that destroy steps can verify their behavior in the check
// function // function
stateBeforeApplication := state.DeepCopy() stateBeforeApplication := state.DeepCopy()