287 lines
10 KiB
Go
287 lines
10 KiB
Go
|
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
|
||
|
}
|