Revert "helper/schema: Implementation of the AsSingle mechanism"
This reverts commit 1987a92386
.
This commit is contained in:
parent
4dbe6add77
commit
1a9c06d0f5
|
@ -18,7 +18,6 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"test_resource": testResource(),
|
||||
"test_resource_as_single": testResourceAsSingle(),
|
||||
"test_resource_gh12183": testResourceGH12183(),
|
||||
"test_resource_import_other": testResourceImportOther(),
|
||||
"test_resource_with_custom_diff": testResourceCustomDiff(),
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func testResourceAsSingle() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: testResourceAsSingleCreate,
|
||||
Read: testResourceAsSingleRead,
|
||||
Delete: testResourceAsSingleDelete,
|
||||
Update: testResourceAsSingleUpdate,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"list_resource_as_block": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"foo": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"list_resource_as_attr": {
|
||||
Type: schema.TypeList,
|
||||
ConfigMode: schema.SchemaConfigModeAttr,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"foo": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"list_primitive": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
"set_resource_as_block": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"foo": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"set_resource_as_attr": {
|
||||
Type: schema.TypeSet,
|
||||
ConfigMode: schema.SchemaConfigModeAttr,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"foo": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"set_primitive": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceAsSingleCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("placeholder")
|
||||
return testResourceAsSingleRead(d, meta)
|
||||
}
|
||||
|
||||
func testResourceAsSingleRead(d *schema.ResourceData, meta interface{}) error {
|
||||
for _, k := range []string{"list_resource_as_block", "list_resource_as_attr", "list_primitive"} {
|
||||
v := d.Get(k)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if l, ok := v.([]interface{}); !ok {
|
||||
return fmt.Errorf("%s should appear as []interface {}, not %T", k, v)
|
||||
} else {
|
||||
for i, item := range l {
|
||||
switch k {
|
||||
case "list_primitive":
|
||||
if _, ok := item.(string); item != nil && !ok {
|
||||
return fmt.Errorf("%s[%d] should appear as string, not %T", k, i, item)
|
||||
}
|
||||
default:
|
||||
if _, ok := item.(map[string]interface{}); item != nil && !ok {
|
||||
return fmt.Errorf("%s[%d] should appear as map[string]interface {}, not %T", k, i, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, k := range []string{"set_resource_as_block", "set_resource_as_attr", "set_primitive"} {
|
||||
v := d.Get(k)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := v.(*schema.Set); !ok {
|
||||
return fmt.Errorf("%s should appear as *schema.Set, not %T", k, v)
|
||||
} else {
|
||||
for i, item := range s.List() {
|
||||
switch k {
|
||||
case "set_primitive":
|
||||
if _, ok := item.(string); item != nil && !ok {
|
||||
return fmt.Errorf("%s[%d] should appear as string, not %T", k, i, item)
|
||||
}
|
||||
default:
|
||||
if _, ok := item.(map[string]interface{}); item != nil && !ok {
|
||||
return fmt.Errorf("%s[%d] should appear as map[string]interface {}, not %T", k, i, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testResourceAsSingleUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
return testResourceAsSingleRead(d, meta)
|
||||
}
|
||||
|
||||
func testResourceAsSingleDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestResourceAsSingle(t *testing.T) {
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckResourceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: strings.TrimSpace(`
|
||||
resource "test_resource_as_single" "foo" {
|
||||
list_resource_as_block {
|
||||
foo = "as block a"
|
||||
}
|
||||
list_resource_as_attr = {
|
||||
foo = "as attr a"
|
||||
}
|
||||
list_primitive = "primitive a"
|
||||
|
||||
set_resource_as_block {
|
||||
foo = "as block a"
|
||||
}
|
||||
set_resource_as_attr = {
|
||||
foo = "as attr a"
|
||||
}
|
||||
set_primitive = "primitive a"
|
||||
}
|
||||
`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
func(s *terraform.State) error {
|
||||
t.Log("state after initial create:\n", s.String())
|
||||
return nil
|
||||
},
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_block.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_block.0.foo", "as block a"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_attr.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_attr.0.foo", "as attr a"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_primitive.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_primitive.0", "primitive a"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_block.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_block.1417230722.foo", "as block a"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_attr.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_attr.2549052262.foo", "as attr a"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_primitive.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_primitive.247272358", "primitive a"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: strings.TrimSpace(`
|
||||
resource "test_resource_as_single" "foo" {
|
||||
list_resource_as_block {
|
||||
foo = "as block b"
|
||||
}
|
||||
list_resource_as_attr = {
|
||||
foo = "as attr b"
|
||||
}
|
||||
list_primitive = "primitive b"
|
||||
|
||||
set_resource_as_block {
|
||||
foo = "as block b"
|
||||
}
|
||||
set_resource_as_attr = {
|
||||
foo = "as attr b"
|
||||
}
|
||||
set_primitive = "primitive b"
|
||||
}
|
||||
`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
func(s *terraform.State) error {
|
||||
t.Log("state after update:\n", s.String())
|
||||
return nil
|
||||
},
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_block.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_block.0.foo", "as block b"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_attr.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_attr.0.foo", "as attr b"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_primitive.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_primitive.0", "primitive b"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_block.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_block.2136238657.foo", "as block b"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_attr.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_attr.3166838949.foo", "as attr b"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_primitive.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_primitive.630210661", "primitive b"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: strings.TrimSpace(`
|
||||
resource "test_resource_as_single" "foo" {
|
||||
}
|
||||
`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
func(s *terraform.State) error {
|
||||
t.Log("state after everything unset:\n", s.String())
|
||||
return nil
|
||||
},
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_block.#", "0"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_resource_as_attr.#", "0"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "list_primitive.#", "0"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_block.#", "0"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_resource_as_attr.#", "0"),
|
||||
resource.TestCheckResourceAttr("test_resource_as_single.foo", "set_primitive.#", "0"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -275,12 +275,6 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE WELL: The AsSingle mechanism cannot be automatically normalized here,
|
||||
// so providers that use it must be ready to handle both normalized and
|
||||
// unnormalized input in their upgrade codepaths. The _result_ of an upgrade
|
||||
// should set a single-element list/set for any AsSingle element so that it
|
||||
// can be normalized to a single value automatically on return.
|
||||
|
||||
// We first need to upgrade a flatmap state if it exists.
|
||||
// There should never be both a JSON and Flatmap state in the request.
|
||||
if req.RawState.Flatmap != nil {
|
||||
|
@ -298,8 +292,6 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
schema.FixupAsSingleConfigValueOut(jsonMap, s.provider.ResourcesMap[req.TypeName].Schema)
|
||||
|
||||
// now we need to turn the state into the default json representation, so
|
||||
// that it can be re-decoded using the actual schema.
|
||||
val, err := schema.JSONMapToStateValue(jsonMap, blockForShimming)
|
||||
|
@ -468,7 +460,6 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
|
|||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
return resp, nil
|
||||
}
|
||||
// res.ShimInstanceStateFromValue result has already had FixupAsSingleInstanceStateIn applied
|
||||
|
||||
newInstanceState, err := res.RefreshWithoutUpgrade(instanceState, s.provider.Meta())
|
||||
if err != nil {
|
||||
|
@ -560,7 +551,6 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
|
|||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
return resp, nil
|
||||
}
|
||||
// res.ShimInstanceStateFromValue result has already had FixupAsSingleInstanceStateIn applied
|
||||
priorPrivate := make(map[string]interface{})
|
||||
if len(req.PriorPrivate) > 0 {
|
||||
if err := json.Unmarshal(req.PriorPrivate, &priorPrivate); err != nil {
|
||||
|
@ -612,17 +602,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
|
|||
}
|
||||
|
||||
// now we need to apply the diff to the prior state, so get the planned state
|
||||
plannedAttrs, err := diff.Apply(priorState.Attributes, res.CoreConfigSchemaWhenShimmed())
|
||||
schema.FixupAsSingleInstanceStateOut(
|
||||
&terraform.InstanceState{Attributes: plannedAttrs},
|
||||
s.provider.ResourcesMap[req.TypeName],
|
||||
)
|
||||
|
||||
// We also fix up the diff for AsSingle here, but note that we intentionally
|
||||
// do it _after_ diff.Apply (so that the state can have its own fixup applied)
|
||||
// but before we deal with requiresNew below so that fixing up the diff
|
||||
// also fixes up the requiresNew keys to match.
|
||||
schema.FixupAsSingleInstanceDiffOut(diff, s.provider.ResourcesMap[req.TypeName])
|
||||
plannedAttrs, err := diff.Apply(priorState.Attributes, block)
|
||||
|
||||
plannedStateVal, err := hcl2shim.HCL2ValueFromFlatmap(plannedAttrs, blockForShimming.ImpliedType())
|
||||
if err != nil {
|
||||
|
@ -763,7 +743,6 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
|||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
return resp, nil
|
||||
}
|
||||
// res.ShimInstanceStateFromValue result has already had FixupAsSingleInstanceStateIn applied
|
||||
|
||||
private := make(map[string]interface{})
|
||||
if len(req.PlannedPrivate) > 0 {
|
||||
|
@ -785,10 +764,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
|||
Destroy: true,
|
||||
}
|
||||
} else {
|
||||
diff, err = schema.DiffFromValues(
|
||||
priorStateVal, plannedStateVal,
|
||||
stripResourceModifiers(res),
|
||||
)
|
||||
diff, err = schema.DiffFromValues(priorStateVal, plannedStateVal, stripResourceModifiers(res))
|
||||
if err != nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
return resp, nil
|
||||
|
@ -802,10 +778,6 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE WELL: schema.DiffFromValues has already effectively applied
|
||||
// schema.FixupAsSingleInstanceDiffIn to the diff, so we need not (and must not)
|
||||
// repeat that here.
|
||||
|
||||
// We need to fix any sets that may be using the "~" index prefix to
|
||||
// indicate partially computed. The special sigil isn't really used except
|
||||
// as a clue to visually indicate that the set isn't wholly known.
|
||||
|
@ -934,8 +906,6 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
|
|||
// copy the ID again just to be sure it wasn't missed
|
||||
is.Attributes["id"] = is.ID
|
||||
|
||||
schema.FixupAsSingleInstanceStateOut(is, s.provider.ResourcesMap[req.TypeName])
|
||||
|
||||
resourceType := is.Ephemeral.Type
|
||||
if resourceType == "" {
|
||||
resourceType = req.TypeName
|
||||
|
@ -1014,7 +984,6 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
|
|||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
return resp, nil
|
||||
}
|
||||
schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.DataSourcesMap[req.TypeName])
|
||||
|
||||
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, blockForShimming.ImpliedType())
|
||||
if err != nil {
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
|
||||
|
@ -153,7 +155,6 @@ func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.R
|
|||
}
|
||||
|
||||
instanceState, err := res.ShimInstanceStateFromValue(rio.Value)
|
||||
// instanceState has already had the AsSingle fixup applied because ShimInstanceStateFromValue calls FixupAsSingleInstanceStateIn.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,286 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// FixupAsSingleResourceConfigIn modifies the given ResourceConfig in-place if
|
||||
// any attributes in the schema have the AsSingle flag set, wrapping the given
|
||||
// values for these in an extra level of slice so that they can be understood
|
||||
// by legacy SDK code that'll be expecting to decode into a list/set.
|
||||
func FixupAsSingleResourceConfigIn(rc *terraform.ResourceConfig, s map[string]*Schema) {
|
||||
if rc == nil {
|
||||
return
|
||||
}
|
||||
FixupAsSingleConfigValueIn(rc.Config, s)
|
||||
}
|
||||
|
||||
// FixupAsSingleInstanceStateIn modifies the given InstanceState in-place if
|
||||
// any attributes in the schema have the AsSingle flag set, adding additional
|
||||
// index steps to the flatmap keys for these so that they can be understood
|
||||
// by legacy SDK code that'll be expecting to decode into a list/set.
|
||||
func FixupAsSingleInstanceStateIn(is *terraform.InstanceState, r *Resource) {
|
||||
fixupAsSingleInstanceState(is, r.Schema, "", fixupAsSingleFlatmapKeysIn)
|
||||
}
|
||||
|
||||
// FixupAsSingleInstanceStateOut modifies the given InstanceState in-place if
|
||||
// any attributes in the schema have the AsSingle flag set, removing unneeded
|
||||
// index steps from the flatmap keys for these so that they can be understood
|
||||
// by the shim back to Terraform Core as a single nested value.
|
||||
func FixupAsSingleInstanceStateOut(is *terraform.InstanceState, r *Resource) {
|
||||
fixupAsSingleInstanceState(is, r.Schema, "", fixupAsSingleFlatmapKeysOut)
|
||||
}
|
||||
|
||||
// FixupAsSingleInstanceDiffIn modifies the given InstanceDiff in-place if any
|
||||
// attributes in the schema have the AsSingle flag set, adding additional index
|
||||
// steps to the flatmap keys for these so that they can be understood by legacy
|
||||
// SDK code that'll be expecting to decode into a list/set.
|
||||
func FixupAsSingleInstanceDiffIn(id *terraform.InstanceDiff, r *Resource) {
|
||||
fixupAsSingleInstanceDiff(id, r.Schema, "", fixupAsSingleAttrsMapKeysIn)
|
||||
}
|
||||
|
||||
// FixupAsSingleInstanceDiffOut modifies the given InstanceDiff in-place if any
|
||||
// attributes in the schema have the AsSingle flag set, removing unneeded index
|
||||
// steps from the flatmap keys for these so that they can be understood by the
|
||||
// shim back to Terraform Core as a single nested value.
|
||||
func FixupAsSingleInstanceDiffOut(id *terraform.InstanceDiff, r *Resource) {
|
||||
fixupAsSingleInstanceDiff(id, r.Schema, "", fixupAsSingleAttrsMapKeysOut)
|
||||
}
|
||||
|
||||
// FixupAsSingleConfigValueIn modifies the given "config value" in-place if
|
||||
// any attributes in the schema have the AsSingle flag set, wrapping the given
|
||||
// values for these in an extra level of slice so that they can be understood
|
||||
// by legacy SDK code that'll be expecting to decode into a list/set.
|
||||
//
|
||||
// "Config value" for the purpose of this function has the same meaning as for
|
||||
// the hcl2shims: a map[string]interface{} using the same subset of Go value
|
||||
// types that would be generated by HCL/HIL when decoding a configuration in
|
||||
// Terraform v0.11.
|
||||
func FixupAsSingleConfigValueIn(c map[string]interface{}, s map[string]*Schema) {
|
||||
for k, as := range s {
|
||||
if !as.AsSingle {
|
||||
continue // Don't touch non-AsSingle values at all. This is explicitly opt-in.
|
||||
}
|
||||
|
||||
v, ok := c[k]
|
||||
if ok {
|
||||
c[k] = []interface{}{v}
|
||||
}
|
||||
|
||||
if nr, ok := as.Elem.(*Resource); ok {
|
||||
// Recursively fixup nested attributes too
|
||||
nm, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
// Weird for a nested resource to not be a map, but we'll tolerate it rather than crashing
|
||||
continue
|
||||
}
|
||||
FixupAsSingleConfigValueIn(nm, nr.Schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FixupAsSingleConfigValueOut modifies the given "config value" in-place if
|
||||
// any attributes in the schema have the AsSingle flag set, unwrapping the
|
||||
// given values from their single-element slices so that they can be understood
|
||||
// as a single object value by Terraform Core.
|
||||
//
|
||||
// This is the opposite of fixupAsSingleConfigValueIn.
|
||||
func FixupAsSingleConfigValueOut(c map[string]interface{}, s map[string]*Schema) {
|
||||
for k, as := range s {
|
||||
if !as.AsSingle {
|
||||
continue // Don't touch non-AsSingle values at all. This is explicitly opt-in.
|
||||
}
|
||||
|
||||
sv, ok := c[k].([]interface{})
|
||||
if ok && len(sv) != 0 { // Should always be a single-element slice, but if not we'll just leave it alone rather than crashing
|
||||
c[k] = sv[0]
|
||||
if nr, ok := as.Elem.(*Resource); ok {
|
||||
// Recursively fixup nested attributes too
|
||||
nm, ok := sv[0].(map[string]interface{})
|
||||
if ok {
|
||||
FixupAsSingleConfigValueOut(nm, nr.Schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fixupAsSingleInstanceState(is *terraform.InstanceState, s map[string]*Schema, prefix string, fn func(map[string]string, string) string) {
|
||||
if is == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, as := range s {
|
||||
if !as.AsSingle {
|
||||
continue // Don't touch non-AsSingle values at all. This is explicitly opt-in.
|
||||
}
|
||||
|
||||
nextPrefix := fn(is.Attributes, prefix+k+".")
|
||||
if nr, ok := as.Elem.(*Resource); ok {
|
||||
// Recursively fixup nested attributes too
|
||||
fixupAsSingleInstanceState(is, nr.Schema, nextPrefix, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fixupAsSingleInstanceDiff(id *terraform.InstanceDiff, s map[string]*Schema, prefix string, fn func(map[string]*terraform.ResourceAttrDiff, string) string) {
|
||||
if id == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, as := range s {
|
||||
if !as.AsSingle {
|
||||
continue // Don't touch non-AsSingle values at all. This is explicitly opt-in.
|
||||
}
|
||||
|
||||
nextPrefix := fn(id.Attributes, prefix+k+".")
|
||||
if nr, ok := as.Elem.(*Resource); ok {
|
||||
// Recursively fixup nested attributes too
|
||||
fixupAsSingleInstanceDiff(id, nr.Schema, nextPrefix, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixupAsSingleFlatmapKeysIn searches the given flatmap for all keys with
|
||||
// the given prefix (which must end with a dot) and replaces them with keys
|
||||
// where that prefix is followed by the dummy index "0." and, if any such
|
||||
// keys are found, a ".#"-suffixed key is also added whose value is "1".
|
||||
//
|
||||
// This function will also replace an exact match of the given prefix with
|
||||
// the trailing dot removed, to recognize values of primitive-typed attributes.
|
||||
func fixupAsSingleFlatmapKeysIn(attrs map[string]string, prefix string) string {
|
||||
ks := make([]string, 0, len(attrs))
|
||||
for k := range attrs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks) // Makes no difference for valid input, but will ensure we handle invalid input deterministically
|
||||
|
||||
for _, k := range ks {
|
||||
newK, countK := fixupAsSingleFlatmapKeyIn(k, prefix)
|
||||
if _, exists := attrs[newK]; k != newK && !exists {
|
||||
attrs[newK] = attrs[k]
|
||||
delete(attrs, k)
|
||||
}
|
||||
if _, exists := attrs[countK]; countK != "" && !exists {
|
||||
attrs[countK] = "1"
|
||||
}
|
||||
}
|
||||
|
||||
return prefix + "0."
|
||||
}
|
||||
|
||||
// fixupAsSingleAttrsMapKeysIn searches the given AttrDiff map for all keys with
|
||||
// the given prefix (which must end with a dot) and replaces them with keys
|
||||
// where that prefix is followed by the dummy index "0." and, if any such
|
||||
// keys are found, a ".#"-suffixed key is also added whose value is "1".
|
||||
//
|
||||
// This function will also replace an exact match of the given prefix with
|
||||
// the trailing dot removed, to recognize values of primitive-typed attributes.
|
||||
func fixupAsSingleAttrsMapKeysIn(attrs map[string]*terraform.ResourceAttrDiff, prefix string) string {
|
||||
ks := make([]string, 0, len(attrs))
|
||||
for k := range attrs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks) // Makes no difference for valid input, but will ensure we handle invalid input deterministically
|
||||
|
||||
for _, k := range ks {
|
||||
newK, countK := fixupAsSingleFlatmapKeyIn(k, prefix)
|
||||
if _, exists := attrs[newK]; k != newK && !exists {
|
||||
attrs[newK] = attrs[k]
|
||||
delete(attrs, k)
|
||||
}
|
||||
if _, exists := attrs[countK]; countK != "" && !exists {
|
||||
attrs[countK] = &terraform.ResourceAttrDiff{
|
||||
Old: "1", // One should _always_ be present, so this seems okay?
|
||||
New: "1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prefix + "0."
|
||||
}
|
||||
|
||||
func fixupAsSingleFlatmapKeyIn(k, prefix string) (string, string) {
|
||||
exact := prefix[:len(prefix)-1]
|
||||
|
||||
switch {
|
||||
case k == exact:
|
||||
return exact + ".0", exact + ".#"
|
||||
case strings.HasPrefix(k, prefix):
|
||||
return prefix + "0." + k[len(prefix):], prefix + "#"
|
||||
default:
|
||||
return k, ""
|
||||
}
|
||||
}
|
||||
|
||||
// fixupAsSingleFlatmapKeysOut searches the given flatmap for all keys with
|
||||
// the given prefix (which must end with a dot) and replaces them with keys
|
||||
// where the following dot-separated label is removed, under the assumption that
|
||||
// it's an index that is no longer needed and, if such a key is present, also
|
||||
// remove the "count" key for the prefix, which is the prefix followed by "#".
|
||||
func fixupAsSingleFlatmapKeysOut(attrs map[string]string, prefix string) string {
|
||||
ks := make([]string, 0, len(attrs))
|
||||
for k := range attrs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks) // Makes no difference for valid input, but will ensure we handle invalid input deterministically
|
||||
|
||||
for _, k := range ks {
|
||||
newK := fixupAsSingleFlatmapKeyOut(k, prefix)
|
||||
if newK != k && newK == "" {
|
||||
delete(attrs, k)
|
||||
} else if _, exists := attrs[newK]; newK != k && !exists {
|
||||
attrs[newK] = attrs[k]
|
||||
delete(attrs, k)
|
||||
}
|
||||
}
|
||||
|
||||
delete(attrs, prefix+"#") // drop the count key, if it's present
|
||||
return prefix
|
||||
}
|
||||
|
||||
// fixupAsSingleAttrsMapKeysOut searches the given AttrDiff map for all keys with
|
||||
// the given prefix (which must end with a dot) and replaces them with keys
|
||||
// where the following dot-separated label is removed, under the assumption that
|
||||
// it's an index that is no longer needed and, if such a key is present, also
|
||||
// remove the "count" key for the prefix, which is the prefix followed by "#".
|
||||
func fixupAsSingleAttrsMapKeysOut(attrs map[string]*terraform.ResourceAttrDiff, prefix string) string {
|
||||
ks := make([]string, 0, len(attrs))
|
||||
for k := range attrs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks) // Makes no difference for valid input, but will ensure we handle invalid input deterministically
|
||||
|
||||
for _, k := range ks {
|
||||
newK := fixupAsSingleFlatmapKeyOut(k, prefix)
|
||||
if newK != k && newK == "" {
|
||||
delete(attrs, k)
|
||||
} else if _, exists := attrs[newK]; newK != k && !exists {
|
||||
attrs[newK] = attrs[k]
|
||||
delete(attrs, k)
|
||||
}
|
||||
}
|
||||
|
||||
delete(attrs, prefix+"#") // drop the count key, if it's present
|
||||
return prefix
|
||||
}
|
||||
|
||||
func fixupAsSingleFlatmapKeyOut(k, prefix string) string {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
remain := k[len(prefix):]
|
||||
if remain == "#" {
|
||||
// Don't need the count element anymore
|
||||
return ""
|
||||
}
|
||||
dotIdx := strings.Index(remain, ".")
|
||||
if dotIdx == -1 {
|
||||
return prefix[:len(prefix)-1] // no follow-on attributes then
|
||||
} else {
|
||||
return prefix + remain[dotIdx+1:] // everything after the next dot
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
|
@ -1,239 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestFixupAsSingleInstanceStateInOut(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
AttrsIn map[string]string
|
||||
AttrsOut map[string]string
|
||||
Schema map[string]*Schema
|
||||
}{
|
||||
"empty": {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
"simple": {
|
||||
map[string]string{
|
||||
"a": "a value",
|
||||
},
|
||||
map[string]string{
|
||||
"a": "a value",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {Type: TypeString, Optional: true},
|
||||
},
|
||||
},
|
||||
"normal list of primitive, empty": {
|
||||
map[string]string{
|
||||
"a.%": "0",
|
||||
},
|
||||
map[string]string{
|
||||
"a.%": "0",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
"normal list of primitive, single": {
|
||||
map[string]string{
|
||||
"a.%": "1",
|
||||
"a.0": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.%": "1",
|
||||
"a.0": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AsSingle list of primitive": {
|
||||
map[string]string{
|
||||
"a": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.#": "1",
|
||||
"a.0": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AsSingle list of resource": {
|
||||
map[string]string{
|
||||
"a.b": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.#": "1",
|
||||
"a.0.b": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"b": {
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AsSingle list of resource with nested primitive list": {
|
||||
map[string]string{
|
||||
"a.b.#": "1",
|
||||
"a.b.0": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.#": "1",
|
||||
"a.0.b.#": "1",
|
||||
"a.0.b.0": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"b": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AsSingle list of resource with nested AsSingle primitive list": {
|
||||
map[string]string{
|
||||
"a.b": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.#": "1",
|
||||
"a.0.b.#": "1",
|
||||
"a.0.b.0": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"b": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AsSingle list of resource with nested AsSingle resource list": {
|
||||
map[string]string{
|
||||
"a.b.c": "hello",
|
||||
},
|
||||
map[string]string{
|
||||
"a.#": "1",
|
||||
"a.0.b.#": "1",
|
||||
"a.0.b.0.c": "hello",
|
||||
},
|
||||
map[string]*Schema{
|
||||
"a": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"b": {
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
AsSingle: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"c": {
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
copyMap := func(m map[string]string) map[string]string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
ret := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
ret[k] = v
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("In", func(t *testing.T) {
|
||||
input := copyMap(test.AttrsIn)
|
||||
is := &terraform.InstanceState{
|
||||
Attributes: input,
|
||||
}
|
||||
r := &Resource{Schema: test.Schema}
|
||||
FixupAsSingleInstanceStateIn(is, r)
|
||||
if !cmp.Equal(is.Attributes, test.AttrsOut) {
|
||||
t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v\n\n%s", input, is.Attributes, test.AttrsOut, cmp.Diff(test.AttrsOut, is.Attributes))
|
||||
}
|
||||
})
|
||||
t.Run("Out", func(t *testing.T) {
|
||||
input := copyMap(test.AttrsOut)
|
||||
is := &terraform.InstanceState{
|
||||
Attributes: input,
|
||||
}
|
||||
r := &Resource{Schema: test.Schema}
|
||||
FixupAsSingleInstanceStateOut(is, r)
|
||||
if !cmp.Equal(is.Attributes, test.AttrsIn) {
|
||||
t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v\n\n%s", input, is.Attributes, test.AttrsIn, cmp.Diff(test.AttrsIn, is.Attributes))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -22,35 +22,6 @@ import (
|
|||
// This method presumes a schema that passes InternalValidate, and so may
|
||||
// panic or produce an invalid result if given an invalid schemaMap.
|
||||
func (m schemaMap) CoreConfigSchema() *configschema.Block {
|
||||
return m.coreConfigSchema(true, true)
|
||||
}
|
||||
|
||||
// CoreConfigSchemaForShimming is a variant of CoreConfigSchema that returns
|
||||
// the schema that should be used when applying our shimming behaviors.
|
||||
//
|
||||
// In particular, it ignores the SkipCoreTypeCheck flag on any legacy schemas,
|
||||
// since the shims live on the SDK side and so they need to see the full
|
||||
// type information that we'd normally hide from Terraform Core when skipping
|
||||
// type checking over there.
|
||||
func (m schemaMap) CoreConfigSchemaForShimming() *configschema.Block {
|
||||
return m.coreConfigSchema(true, false)
|
||||
}
|
||||
|
||||
// CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns
|
||||
// the schema as it would appear when working with data structures that have
|
||||
// already been shimmed to the legacy form.
|
||||
//
|
||||
// In particular, it ignores the AsSingle flag on any legacy schemas and behaves
|
||||
// as if they were really lists/sets instead, thus giving a description of
|
||||
// the shape of the data structure after the AsSingle fixup has been applied.
|
||||
//
|
||||
// This should be used with care only in unusual situations where we need to
|
||||
// work with an already-shimmed value using a new-style schema.
|
||||
func (m schemaMap) CoreConfigSchemaWhenShimmed() *configschema.Block {
|
||||
return m.coreConfigSchema(false, false)
|
||||
}
|
||||
|
||||
func (m schemaMap) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.Block {
|
||||
if len(m) == 0 {
|
||||
// We return an actual (empty) object here, rather than a nil,
|
||||
// because a nil result would mean that we don't have a schema at
|
||||
|
@ -65,7 +36,7 @@ func (m schemaMap) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.
|
|||
|
||||
for name, schema := range m {
|
||||
if schema.Elem == nil {
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||
continue
|
||||
}
|
||||
if schema.Type == TypeMap {
|
||||
|
@ -79,27 +50,27 @@ func (m schemaMap) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.
|
|||
sch.Elem = &Schema{
|
||||
Type: TypeString,
|
||||
}
|
||||
ret.Attributes[name] = sch.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
|
||||
ret.Attributes[name] = sch.coreConfigSchemaAttribute()
|
||||
continue
|
||||
}
|
||||
}
|
||||
switch schema.ConfigMode {
|
||||
case SchemaConfigModeAttr:
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||
case SchemaConfigModeBlock:
|
||||
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
|
||||
ret.BlockTypes[name] = schema.coreConfigSchemaBlock()
|
||||
default: // SchemaConfigModeAuto, or any other invalid value
|
||||
if schema.Computed && !schema.Optional {
|
||||
// Computed-only schemas are always handled as attributes,
|
||||
// because they never appear in configuration.
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||
continue
|
||||
}
|
||||
switch schema.Elem.(type) {
|
||||
case *Schema, ValueType:
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
|
||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||
case *Resource:
|
||||
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
|
||||
ret.BlockTypes[name] = schema.coreConfigSchemaBlock()
|
||||
default:
|
||||
// Should never happen for a valid schema
|
||||
panic(fmt.Errorf("invalid Schema.Elem %#v; need *Schema or *Resource", schema.Elem))
|
||||
|
@ -114,7 +85,7 @@ func (m schemaMap) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.
|
|||
// of a schema. This is appropriate only for primitives or collections whose
|
||||
// Elem is an instance of Schema. Use coreConfigSchemaBlock for collections
|
||||
// whose elem is a whole resource.
|
||||
func (s *Schema) coreConfigSchemaAttribute(asSingle, skipCoreCheck bool) *configschema.Attribute {
|
||||
func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute {
|
||||
// The Schema.DefaultFunc capability adds some extra weirdness here since
|
||||
// it can be combined with "Required: true" to create a sitution where
|
||||
// required-ness is conditional. Terraform Core doesn't share this concept,
|
||||
|
@ -145,7 +116,7 @@ func (s *Schema) coreConfigSchemaAttribute(asSingle, skipCoreCheck bool) *config
|
|||
}
|
||||
|
||||
return &configschema.Attribute{
|
||||
Type: s.coreConfigSchemaType(asSingle, skipCoreCheck),
|
||||
Type: s.coreConfigSchemaType(),
|
||||
Optional: opt,
|
||||
Required: reqd,
|
||||
Computed: s.Computed,
|
||||
|
@ -157,9 +128,9 @@ func (s *Schema) coreConfigSchemaAttribute(asSingle, skipCoreCheck bool) *config
|
|||
// coreConfigSchemaBlock prepares a configschema.NestedBlock representation of
|
||||
// a schema. This is appropriate only for collections whose Elem is an instance
|
||||
// of Resource, and will panic otherwise.
|
||||
func (s *Schema) coreConfigSchemaBlock(asSingle, skipCoreCheck bool) *configschema.NestedBlock {
|
||||
func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock {
|
||||
ret := &configschema.NestedBlock{}
|
||||
if nested := schemaMap(s.Elem.(*Resource).Schema).coreConfigSchema(asSingle, skipCoreCheck); nested != nil {
|
||||
if nested := s.Elem.(*Resource).coreConfigSchema(); nested != nil {
|
||||
ret.Block = *nested
|
||||
}
|
||||
switch s.Type {
|
||||
|
@ -177,14 +148,6 @@ func (s *Schema) coreConfigSchemaBlock(asSingle, skipCoreCheck bool) *configsche
|
|||
ret.MinItems = s.MinItems
|
||||
ret.MaxItems = s.MaxItems
|
||||
|
||||
if s.AsSingle && asSingle {
|
||||
// In AsSingle mode, we artifically force a TypeList or TypeSet
|
||||
// attribute in the SDK to be treated as a single block by Terraform Core.
|
||||
// This must then be fixed up in the shim code (in helper/plugin) so
|
||||
// that the SDK still sees the lists or sets it's expecting.
|
||||
ret.Nesting = configschema.NestingSingle
|
||||
}
|
||||
|
||||
if s.Required && s.MinItems == 0 {
|
||||
// configschema doesn't have a "required" representation for nested
|
||||
// blocks, but we can fake it by requiring at least one item.
|
||||
|
@ -210,15 +173,7 @@ func (s *Schema) coreConfigSchemaBlock(asSingle, skipCoreCheck bool) *configsche
|
|||
|
||||
// coreConfigSchemaType determines the core config schema type that corresponds
|
||||
// to a particular schema's type.
|
||||
func (s *Schema) coreConfigSchemaType(asSingle, skipCoreCheck bool) cty.Type {
|
||||
if skipCoreCheck && s.SkipCoreTypeCheck {
|
||||
// If we're preparing a schema for Terraform Core and the schema is
|
||||
// asking us to skip the Core type-check then we'll tell core that this
|
||||
// attribute is dynamically-typed, so it'll just pass through anything
|
||||
// and let us validate it on the plugin side.
|
||||
return cty.DynamicPseudoType
|
||||
}
|
||||
|
||||
func (s *Schema) coreConfigSchemaType() cty.Type {
|
||||
switch s.Type {
|
||||
case TypeString:
|
||||
return cty.String
|
||||
|
@ -233,17 +188,17 @@ func (s *Schema) coreConfigSchemaType(asSingle, skipCoreCheck bool) cty.Type {
|
|||
var elemType cty.Type
|
||||
switch set := s.Elem.(type) {
|
||||
case *Schema:
|
||||
elemType = set.coreConfigSchemaType(asSingle, skipCoreCheck)
|
||||
elemType = set.coreConfigSchemaType()
|
||||
case ValueType:
|
||||
// This represents a mistake in the provider code, but it's a
|
||||
// common one so we'll just shim it.
|
||||
elemType = (&Schema{Type: set}).coreConfigSchemaType(asSingle, skipCoreCheck)
|
||||
elemType = (&Schema{Type: set}).coreConfigSchemaType()
|
||||
case *Resource:
|
||||
// By default we construct a NestedBlock in this case, but this
|
||||
// behavior is selected either for computed-only schemas or
|
||||
// when ConfigMode is explicitly SchemaConfigModeBlock.
|
||||
// See schemaMap.CoreConfigSchema for the exact rules.
|
||||
elemType = schemaMap(set.Schema).coreConfigSchema(asSingle, skipCoreCheck).ImpliedType()
|
||||
elemType = set.coreConfigSchema().ImpliedType()
|
||||
default:
|
||||
if set != nil {
|
||||
// Should never happen for a valid schema
|
||||
|
@ -253,13 +208,6 @@ func (s *Schema) coreConfigSchemaType(asSingle, skipCoreCheck bool) cty.Type {
|
|||
// to be compatible with them.
|
||||
elemType = cty.String
|
||||
}
|
||||
if s.AsSingle && asSingle {
|
||||
// In AsSingle mode, we artifically force a TypeList or TypeSet
|
||||
// attribute in the SDK to be treated as a single value by Terraform Core.
|
||||
// This must then be fixed up in the shim code (in helper/plugin) so
|
||||
// that the SDK still sees the lists or sets it's expecting.
|
||||
return elemType
|
||||
}
|
||||
switch s.Type {
|
||||
case TypeList:
|
||||
return cty.List(elemType)
|
||||
|
@ -281,34 +229,7 @@ func (s *Schema) coreConfigSchemaType(asSingle, skipCoreCheck bool) cty.Type {
|
|||
// the resource's schema. CoreConfigSchema adds the implicitly required "id"
|
||||
// attribute for top level resources if it doesn't exist.
|
||||
func (r *Resource) CoreConfigSchema() *configschema.Block {
|
||||
return r.coreConfigSchema(true, true)
|
||||
}
|
||||
|
||||
// CoreConfigSchemaForShimming is a variant of CoreConfigSchema that returns
|
||||
// the schema that should be used to apply shims on the SDK side.
|
||||
//
|
||||
// In particular, it ignores the SkipCoreTypeCheck flag on any legacy schemas
|
||||
// and uses the real type information instead.
|
||||
func (r *Resource) CoreConfigSchemaForShimming() *configschema.Block {
|
||||
return r.coreConfigSchema(true, false)
|
||||
}
|
||||
|
||||
// CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns
|
||||
// the schema as it would appear when working with data structures that have
|
||||
// already been shimmed to the legacy form.
|
||||
//
|
||||
// In particular, it ignores the AsSingle flag on any legacy schemas and behaves
|
||||
// as if they were really lists/sets instead, thus giving a description of
|
||||
// the shape of the data structure after the AsSingle fixup has been applied.
|
||||
//
|
||||
// This should be used with care only in unusual situations where we need to
|
||||
// work with an already-shimmed value using a new-style schema.
|
||||
func (r *Resource) CoreConfigSchemaWhenShimmed() *configschema.Block {
|
||||
return r.coreConfigSchema(false, false)
|
||||
}
|
||||
|
||||
func (r *Resource) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.Block {
|
||||
block := schemaMap(r.Schema).coreConfigSchema(asSingle, skipCoreCheck)
|
||||
block := r.coreConfigSchema()
|
||||
|
||||
if block.Attributes == nil {
|
||||
block.Attributes = map[string]*configschema.Attribute{}
|
||||
|
@ -377,6 +298,10 @@ func (r *Resource) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.
|
|||
return block
|
||||
}
|
||||
|
||||
func (r *Resource) coreConfigSchema() *configschema.Block {
|
||||
return schemaMap(r.Schema).CoreConfigSchema()
|
||||
}
|
||||
|
||||
// CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema
|
||||
// on the backends's schema.
|
||||
func (r *Backend) CoreConfigSchema() *configschema.Block {
|
||||
|
|
|
@ -162,8 +162,6 @@ func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.Insta
|
|||
// match those from the Schema.
|
||||
s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion)
|
||||
|
||||
FixupAsSingleInstanceStateIn(s, r)
|
||||
|
||||
// We now rebuild the state through the ResourceData, so that the set indexes
|
||||
// match what helper/schema expects.
|
||||
data, err := schemaMap(r.Schema).Data(s, nil)
|
||||
|
|
|
@ -14,10 +14,6 @@ import (
|
|||
// derives a terraform.InstanceDiff to give to the legacy providers. This is
|
||||
// used to take the states provided by the new ApplyResourceChange method and
|
||||
// convert them to a state+diff required for the legacy Apply method.
|
||||
//
|
||||
// If the fixup function is non-nil, it will be called with the constructed
|
||||
// shimmed InstanceState and ResourceConfig values to do any necessary in-place
|
||||
// mutations before producing the diff.
|
||||
func DiffFromValues(prior, planned cty.Value, res *Resource) (*terraform.InstanceDiff, error) {
|
||||
return diffFromValues(prior, planned, res, nil)
|
||||
}
|
||||
|
@ -28,7 +24,6 @@ func DiffFromValues(prior, planned cty.Value, res *Resource) (*terraform.Instanc
|
|||
// have already been done.
|
||||
func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*terraform.InstanceDiff, error) {
|
||||
instanceState, err := res.ShimInstanceStateFromValue(prior)
|
||||
// The result of ShimInstanceStateFromValue already has FixupAsSingleInstanceStateIn applied
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -36,7 +31,6 @@ func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffF
|
|||
configSchema := res.CoreConfigSchema()
|
||||
|
||||
cfg := terraform.NewResourceConfigShimmed(planned, configSchema)
|
||||
FixupAsSingleResourceConfigIn(cfg, schemaMap(res.Schema))
|
||||
|
||||
diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue