Merge pull request #19536 from hashicorp/jbardin/state-funcs
Handle StateFunc in plugin shims
This commit is contained in:
commit
7cec01a883
|
@ -25,6 +25,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"test_resource_force_new": testResourceForceNew(),
|
"test_resource_force_new": testResourceForceNew(),
|
||||||
"test_resource_nested": testResourceNested(),
|
"test_resource_nested": testResourceNested(),
|
||||||
"test_resource_nested_set": testResourceNestedSet(),
|
"test_resource_nested_set": testResourceNestedSet(),
|
||||||
|
"test_resource_state_func": testResourceStateFunc(),
|
||||||
},
|
},
|
||||||
DataSourcesMap: map[string]*schema.Resource{
|
DataSourcesMap: map[string]*schema.Resource{
|
||||||
"test_data_source": testDataSource(),
|
"test_data_source": testDataSource(),
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testResourceStateFunc() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: testResourceStateFuncCreate,
|
||||||
|
Read: testResourceStateFuncRead,
|
||||||
|
Update: testResourceStateFuncUpdate,
|
||||||
|
Delete: testResourceStateFuncDelete,
|
||||||
|
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"optional": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"state_func": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
StateFunc: stateFuncHash,
|
||||||
|
},
|
||||||
|
"state_func_value": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateFuncHash(v interface{}) string {
|
||||||
|
hash := sha1.Sum([]byte(v.(string)))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResourceStateFuncCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
d.SetId(fmt.Sprintf("%x", rand.Int63()))
|
||||||
|
|
||||||
|
// if we have a reference for the actual data in the state_func field,
|
||||||
|
// compare it
|
||||||
|
if data, ok := d.GetOk("state_func_value"); ok {
|
||||||
|
expected := data.(string)
|
||||||
|
got := d.Get("state_func").(string)
|
||||||
|
if expected != got {
|
||||||
|
return fmt.Errorf("expected state_func value:%q, got%q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return testResourceStateFuncRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResourceStateFuncRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResourceStateFuncUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResourceStateFuncDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResourceStateFunc_basic(t *testing.T) {
|
||||||
|
resource.UnitTest(t, resource.TestCase{
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckResourceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: strings.TrimSpace(`
|
||||||
|
resource "test_resource_state_func" "foo" {
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.TestCheckNoResourceAttr("test_resource_state_func.foo", "state_func"),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: strings.TrimSpace(`
|
||||||
|
resource "test_resource_state_func" "foo" {
|
||||||
|
state_func = "data"
|
||||||
|
state_func_value = "data"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("data")),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: strings.TrimSpace(`
|
||||||
|
resource "test_resource_state_func" "foo" {
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.TestCheckNoResourceAttr("test_resource_state_func.foo", "state_func"),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: strings.TrimSpace(`
|
||||||
|
resource "test_resource_state_func" "foo" {
|
||||||
|
optional = "added"
|
||||||
|
state_func = "data"
|
||||||
|
state_func_value = "data"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("data")),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: strings.TrimSpace(`
|
||||||
|
resource "test_resource_state_func" "foo" {
|
||||||
|
optional = "added"
|
||||||
|
state_func = "changed"
|
||||||
|
state_func_value = "changed"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("changed")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const newExtraKey = "_new_extra_shim"
|
||||||
|
|
||||||
// NewGRPCProviderServerShim wraps a terraform.ResourceProvider in a
|
// NewGRPCProviderServerShim wraps a terraform.ResourceProvider in a
|
||||||
// proto.ProviderServer implementation. If the provided provider is not a
|
// proto.ProviderServer implementation. If the provided provider is not a
|
||||||
// *schema.Provider, this will return nil,
|
// *schema.Provider, this will return nil,
|
||||||
|
@ -563,16 +565,30 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
|
||||||
Msgpack: plannedMP,
|
Msgpack: plannedMP,
|
||||||
}
|
}
|
||||||
|
|
||||||
// the Meta field gets encoded into PlannedPrivate
|
// Now we need to store any NewExtra values, which are where any actual
|
||||||
if diff.Meta != nil {
|
// StateFunc modified config fields are hidden.
|
||||||
plannedPrivate, err := json.Marshal(diff.Meta)
|
privateMap := diff.Meta
|
||||||
if err != nil {
|
if privateMap == nil {
|
||||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
privateMap = map[string]interface{}{}
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
resp.PlannedPrivate = plannedPrivate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newExtra := map[string]interface{}{}
|
||||||
|
|
||||||
|
for k, v := range diff.Attributes {
|
||||||
|
if v.NewExtra != nil {
|
||||||
|
newExtra[k] = v.NewExtra
|
||||||
|
}
|
||||||
|
}
|
||||||
|
privateMap[newExtraKey] = newExtra
|
||||||
|
|
||||||
|
// the Meta field gets encoded into PlannedPrivate
|
||||||
|
plannedPrivate, err := json.Marshal(privateMap)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
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.
|
||||||
var requiresNew []string
|
var requiresNew []string
|
||||||
|
@ -653,7 +669,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
||||||
Destroy: true,
|
Destroy: true,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
diff, err = schema.DiffFromValues(priorStateVal, plannedStateVal, res)
|
diff, err = schema.DiffFromValues(priorStateVal, plannedStateVal, stripResourceModifiers(res))
|
||||||
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
|
||||||
|
@ -667,9 +683,23 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add NewExtra Fields that may have been stored in the private data
|
||||||
|
if newExtra := private[newExtraKey]; newExtra != nil {
|
||||||
|
for k, v := range newExtra.(map[string]interface{}) {
|
||||||
|
d := diff.Attributes[k]
|
||||||
|
|
||||||
|
if d == nil {
|
||||||
|
d = &terraform.ResourceAttrDiff{}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.NewExtra = v
|
||||||
|
diff.Attributes[k] = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// strip out non-diffs
|
// strip out non-diffs
|
||||||
for k, v := range diff.Attributes {
|
for k, v := range diff.Attributes {
|
||||||
if v.New == v.Old && !v.NewComputed && !v.NewRemoved {
|
if v.New == v.Old && !v.NewComputed && !v.NewRemoved && v.NewExtra == "" {
|
||||||
delete(diff.Attributes, k)
|
delete(diff.Attributes, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,11 +714,10 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we use the planned state to check for unknown/zero containers values
|
|
||||||
// when normalizing the flatmap.
|
|
||||||
plannedState := hcl2shim.FlatmapValueFromHCL2(plannedStateVal)
|
|
||||||
|
|
||||||
if newInstanceState != nil {
|
if newInstanceState != nil {
|
||||||
|
// here we use the planned state to check for unknown/zero containers values
|
||||||
|
// when normalizing the flatmap.
|
||||||
|
plannedState := hcl2shim.FlatmapValueFromHCL2(plannedStateVal)
|
||||||
newInstanceState.Attributes = normalizeFlatmapContainers(plannedState, newInstanceState.Attributes)
|
newInstanceState.Attributes = normalizeFlatmapContainers(plannedState, newInstanceState.Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -972,3 +1001,45 @@ func copyTimeoutValues(to cty.Value, from cty.Value) cty.Value {
|
||||||
|
|
||||||
return cty.ObjectVal(toAttrs)
|
return cty.ObjectVal(toAttrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stripResourceModifiers takes a *schema.Resource and returns a deep copy with all
|
||||||
|
// StateFuncs and CustomizeDiffs removed. This will be used during apply to
|
||||||
|
// create a diff from a planned state where the diff modifications have already
|
||||||
|
// been applied.
|
||||||
|
func stripResourceModifiers(r *schema.Resource) *schema.Resource {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// start with a shallow copy
|
||||||
|
newResource := new(schema.Resource)
|
||||||
|
*newResource = *r
|
||||||
|
|
||||||
|
newResource.CustomizeDiff = nil
|
||||||
|
newResource.Schema = map[string]*schema.Schema{}
|
||||||
|
|
||||||
|
for k, s := range r.Schema {
|
||||||
|
newResource.Schema[k] = stripSchema(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripSchema(s *schema.Schema) *schema.Schema {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// start with a shallow copy
|
||||||
|
newSchema := new(schema.Schema)
|
||||||
|
*newSchema = *s
|
||||||
|
|
||||||
|
newSchema.StateFunc = nil
|
||||||
|
|
||||||
|
switch e := newSchema.Elem.(type) {
|
||||||
|
case *schema.Schema:
|
||||||
|
newSchema.Elem = stripSchema(e)
|
||||||
|
case *schema.Resource:
|
||||||
|
newSchema.Elem = stripResourceModifiers(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSchema
|
||||||
|
}
|
||||||
|
|
|
@ -358,6 +358,13 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest)
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
propMP, err := msgpack.Marshal(r.ProposedNewState, resSchema.Block.ImpliedType())
|
propMP, err := msgpack.Marshal(r.ProposedNewState, resSchema.Block.ImpliedType())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
|
@ -367,6 +374,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest)
|
||||||
protoReq := &proto.PlanResourceChange_Request{
|
protoReq := &proto.PlanResourceChange_Request{
|
||||||
TypeName: r.TypeName,
|
TypeName: r.TypeName,
|
||||||
PriorState: &proto.DynamicValue{Msgpack: priorMP},
|
PriorState: &proto.DynamicValue{Msgpack: priorMP},
|
||||||
|
Config: &proto.DynamicValue{Msgpack: configMP},
|
||||||
ProposedNewState: &proto.DynamicValue{Msgpack: propMP},
|
ProposedNewState: &proto.DynamicValue{Msgpack: propMP},
|
||||||
PriorPrivate: r.PriorPrivate,
|
PriorPrivate: r.PriorPrivate,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue