github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/objchange/compatible.go (about)

     1  package objchange
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  	"github.com/zclconf/go-cty/cty/convert"
     9  
    10  	"github.com/hashicorp/terraform/internal/configs/configschema"
    11  	"github.com/hashicorp/terraform/internal/lang/marks"
    12  )
    13  
    14  // AssertObjectCompatible checks whether the given "actual" value is a valid
    15  // completion of the possibly-partially-unknown "planned" value.
    16  //
    17  // This means that any known leaf value in "planned" must be equal to the
    18  // corresponding value in "actual", and various other similar constraints.
    19  //
    20  // Any inconsistencies are reported by returning a non-zero number of errors.
    21  // These errors are usually (but not necessarily) cty.PathError values
    22  // referring to a particular nested value within the "actual" value.
    23  //
    24  // The two values must have types that conform to the given schema's implied
    25  // type, or this function will panic.
    26  func AssertObjectCompatible(schema *configschema.Block, planned, actual cty.Value) []error {
    27  	return assertObjectCompatible(schema, planned, actual, nil)
    28  }
    29  
    30  func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Value, path cty.Path) []error {
    31  	var errs []error
    32  	var atRoot string
    33  	if len(path) == 0 {
    34  		atRoot = "Root resource "
    35  	}
    36  
    37  	if planned.IsNull() && !actual.IsNull() {
    38  		errs = append(errs, path.NewErrorf(fmt.Sprintf("%swas absent, but now present", atRoot)))
    39  		return errs
    40  	}
    41  	if actual.IsNull() && !planned.IsNull() {
    42  		errs = append(errs, path.NewErrorf(fmt.Sprintf("%swas present, but now absent", atRoot)))
    43  		return errs
    44  	}
    45  	if planned.IsNull() {
    46  		// No further checks possible if both values are null
    47  		return errs
    48  	}
    49  
    50  	for name, attrS := range schema.Attributes {
    51  		plannedV := planned.GetAttr(name)
    52  		actualV := actual.GetAttr(name)
    53  
    54  		path := append(path, cty.GetAttrStep{Name: name})
    55  
    56  		// Unmark values here before checking value assertions,
    57  		// but save the marks so we can see if we should supress
    58  		// exposing a value through errors
    59  		unmarkedActualV, marksA := actualV.UnmarkDeep()
    60  		unmarkedPlannedV, marksP := plannedV.UnmarkDeep()
    61  		_, isSensitiveActual := marksA[marks.Sensitive]
    62  		_, isSensitivePlanned := marksP[marks.Sensitive]
    63  
    64  		moreErrs := assertValueCompatible(unmarkedPlannedV, unmarkedActualV, path)
    65  		if attrS.Sensitive || isSensitiveActual || isSensitivePlanned {
    66  			if len(moreErrs) > 0 {
    67  				// Use a vague placeholder message instead, to avoid disclosing
    68  				// sensitive information.
    69  				errs = append(errs, path.NewErrorf("inconsistent values for sensitive attribute"))
    70  			}
    71  		} else {
    72  			errs = append(errs, moreErrs...)
    73  		}
    74  	}
    75  	for name, blockS := range schema.BlockTypes {
    76  		plannedV, _ := planned.GetAttr(name).Unmark()
    77  		actualV, _ := actual.GetAttr(name).Unmark()
    78  
    79  		path := append(path, cty.GetAttrStep{Name: name})
    80  		switch blockS.Nesting {
    81  		case configschema.NestingSingle, configschema.NestingGroup:
    82  			// If an unknown block placeholder was present then the placeholder
    83  			// may have expanded out into zero blocks, which is okay.
    84  			if !plannedV.IsKnown() && actualV.IsNull() {
    85  				continue
    86  			}
    87  			moreErrs := assertObjectCompatible(&blockS.Block, plannedV, actualV, path)
    88  			errs = append(errs, moreErrs...)
    89  		case configschema.NestingList:
    90  			// A NestingList might either be a list or a tuple, depending on
    91  			// whether there are dynamically-typed attributes inside. However,
    92  			// both support a similar-enough API that we can treat them the
    93  			// same for our purposes here.
    94  			if !plannedV.IsKnown() || !actualV.IsKnown() || plannedV.IsNull() || actualV.IsNull() {
    95  				continue
    96  			}
    97  
    98  			plannedL := plannedV.LengthInt()
    99  			actualL := actualV.LengthInt()
   100  			if plannedL != actualL {
   101  				errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL))
   102  				continue
   103  			}
   104  			for it := plannedV.ElementIterator(); it.Next(); {
   105  				idx, plannedEV := it.Element()
   106  				if !actualV.HasIndex(idx).True() {
   107  					continue
   108  				}
   109  				actualEV := actualV.Index(idx)
   110  				moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx}))
   111  				errs = append(errs, moreErrs...)
   112  			}
   113  		case configschema.NestingMap:
   114  			// A NestingMap might either be a map or an object, depending on
   115  			// whether there are dynamically-typed attributes inside, but
   116  			// that's decided statically and so both values will have the same
   117  			// kind.
   118  			if plannedV.Type().IsObjectType() {
   119  				plannedAtys := plannedV.Type().AttributeTypes()
   120  				actualAtys := actualV.Type().AttributeTypes()
   121  				for k := range plannedAtys {
   122  					if _, ok := actualAtys[k]; !ok {
   123  						errs = append(errs, path.NewErrorf("block key %q has vanished", k))
   124  						continue
   125  					}
   126  
   127  					plannedEV := plannedV.GetAttr(k)
   128  					actualEV := actualV.GetAttr(k)
   129  					moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k}))
   130  					errs = append(errs, moreErrs...)
   131  				}
   132  				if plannedV.IsKnown() { // new blocks may appear if unknown blocks were present in the plan
   133  					for k := range actualAtys {
   134  						if _, ok := plannedAtys[k]; !ok {
   135  							errs = append(errs, path.NewErrorf("new block key %q has appeared", k))
   136  							continue
   137  						}
   138  					}
   139  				}
   140  			} else {
   141  				if !plannedV.IsKnown() || plannedV.IsNull() || actualV.IsNull() {
   142  					continue
   143  				}
   144  				plannedL := plannedV.LengthInt()
   145  				actualL := actualV.LengthInt()
   146  				if plannedL != actualL && plannedV.IsKnown() { // new blocks may appear if unknown blocks were persent in the plan
   147  					errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL))
   148  					continue
   149  				}
   150  				for it := plannedV.ElementIterator(); it.Next(); {
   151  					idx, plannedEV := it.Element()
   152  					if !actualV.HasIndex(idx).True() {
   153  						continue
   154  					}
   155  					actualEV := actualV.Index(idx)
   156  					moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx}))
   157  					errs = append(errs, moreErrs...)
   158  				}
   159  			}
   160  		case configschema.NestingSet:
   161  			if !plannedV.IsKnown() || !actualV.IsKnown() || plannedV.IsNull() || actualV.IsNull() {
   162  				continue
   163  			}
   164  
   165  			if !plannedV.IsKnown() {
   166  				// When unknown blocks are present the final number of blocks
   167  				// may be different, either because the unknown set values
   168  				// become equal and are collapsed, or the count is unknown due
   169  				// a dynamic block. Unfortunately this means we can't do our
   170  				// usual checks in this case without generating false
   171  				// negatives.
   172  				continue
   173  			}
   174  
   175  			setErrs := assertSetValuesCompatible(plannedV, actualV, path, func(plannedEV, actualEV cty.Value) bool {
   176  				errs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: actualEV}))
   177  				return len(errs) == 0
   178  			})
   179  			errs = append(errs, setErrs...)
   180  
   181  			// There can be fewer elements in a set after its elements are all
   182  			// known (values that turn out to be equal will coalesce) but the
   183  			// number of elements must never get larger.
   184  			plannedL := plannedV.LengthInt()
   185  			actualL := actualV.LengthInt()
   186  			if plannedL < actualL {
   187  				errs = append(errs, path.NewErrorf("block set length changed from %d to %d", plannedL, actualL))
   188  			}
   189  		default:
   190  			panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting))
   191  		}
   192  	}
   193  	return errs
   194  }
   195  
   196  func assertValueCompatible(planned, actual cty.Value, path cty.Path) []error {
   197  	// NOTE: We don't normally use the GoString rendering of cty.Value in
   198  	// user-facing error messages as a rule, but we make an exception
   199  	// for this function because we expect the user to pass this message on
   200  	// verbatim to the provider development team and so more detail is better.
   201  
   202  	var errs []error
   203  	if planned.Type() == cty.DynamicPseudoType {
   204  		// Anything goes, then
   205  		return errs
   206  	}
   207  	if problems := actual.Type().TestConformance(planned.Type()); len(problems) > 0 {
   208  		errs = append(errs, path.NewErrorf("wrong final value type: %s", convert.MismatchMessage(actual.Type(), planned.Type())))
   209  		// If the types don't match then we can't do any other comparisons,
   210  		// so we bail early.
   211  		return errs
   212  	}
   213  
   214  	if !planned.IsKnown() {
   215  		// We didn't know what were going to end up with during plan, so
   216  		// anything goes during apply.
   217  		return errs
   218  	}
   219  
   220  	if actual.IsNull() {
   221  		if planned.IsNull() {
   222  			return nil
   223  		}
   224  		errs = append(errs, path.NewErrorf("was %#v, but now null", planned))
   225  		return errs
   226  	}
   227  	if planned.IsNull() {
   228  		errs = append(errs, path.NewErrorf("was null, but now %#v", actual))
   229  		return errs
   230  	}
   231  
   232  	ty := planned.Type()
   233  	switch {
   234  
   235  	case !actual.IsKnown():
   236  		errs = append(errs, path.NewErrorf("was known, but now unknown"))
   237  
   238  	case ty.IsPrimitiveType():
   239  		if !actual.Equals(planned).True() {
   240  			errs = append(errs, path.NewErrorf("was %#v, but now %#v", planned, actual))
   241  		}
   242  
   243  	case ty.IsListType() || ty.IsMapType() || ty.IsTupleType():
   244  		for it := planned.ElementIterator(); it.Next(); {
   245  			k, plannedV := it.Element()
   246  			if !actual.HasIndex(k).True() {
   247  				errs = append(errs, path.NewErrorf("element %s has vanished", indexStrForErrors(k)))
   248  				continue
   249  			}
   250  
   251  			actualV := actual.Index(k)
   252  			moreErrs := assertValueCompatible(plannedV, actualV, append(path, cty.IndexStep{Key: k}))
   253  			errs = append(errs, moreErrs...)
   254  		}
   255  
   256  		for it := actual.ElementIterator(); it.Next(); {
   257  			k, _ := it.Element()
   258  			if !planned.HasIndex(k).True() {
   259  				errs = append(errs, path.NewErrorf("new element %s has appeared", indexStrForErrors(k)))
   260  			}
   261  		}
   262  
   263  	case ty.IsObjectType():
   264  		atys := ty.AttributeTypes()
   265  		for name := range atys {
   266  			// Because we already tested that the two values have the same type,
   267  			// we can assume that the same attributes are present in both and
   268  			// focus just on testing their values.
   269  			plannedV := planned.GetAttr(name)
   270  			actualV := actual.GetAttr(name)
   271  			moreErrs := assertValueCompatible(plannedV, actualV, append(path, cty.GetAttrStep{Name: name}))
   272  			errs = append(errs, moreErrs...)
   273  		}
   274  
   275  	case ty.IsSetType():
   276  		// We can't really do anything useful for sets here because changing
   277  		// an unknown element to known changes the identity of the element, and
   278  		// so we can't correlate them properly. However, we will at least check
   279  		// to ensure that the number of elements is consistent, along with
   280  		// the general type-match checks we ran earlier in this function.
   281  		if planned.IsKnown() && !planned.IsNull() && !actual.IsNull() {
   282  
   283  			setErrs := assertSetValuesCompatible(planned, actual, path, func(plannedV, actualV cty.Value) bool {
   284  				errs := assertValueCompatible(plannedV, actualV, append(path, cty.IndexStep{Key: actualV}))
   285  				return len(errs) == 0
   286  			})
   287  			errs = append(errs, setErrs...)
   288  
   289  			// There can be fewer elements in a set after its elements are all
   290  			// known (values that turn out to be equal will coalesce) but the
   291  			// number of elements must never get larger.
   292  
   293  			plannedL := planned.LengthInt()
   294  			actualL := actual.LengthInt()
   295  			if plannedL < actualL {
   296  				errs = append(errs, path.NewErrorf("length changed from %d to %d", plannedL, actualL))
   297  			}
   298  		}
   299  	}
   300  
   301  	return errs
   302  }
   303  
   304  func indexStrForErrors(v cty.Value) string {
   305  	switch v.Type() {
   306  	case cty.Number:
   307  		return v.AsBigFloat().Text('f', -1)
   308  	case cty.String:
   309  		return strconv.Quote(v.AsString())
   310  	default:
   311  		// Should be impossible, since no other index types are allowed!
   312  		return fmt.Sprintf("%#v", v)
   313  	}
   314  }
   315  
   316  // assertSetValuesCompatible checks that each of the elements in a can
   317  // be correlated with at least one equivalent element in b and vice-versa,
   318  // using the given correlation function.
   319  //
   320  // This allows the number of elements in the sets to change as long as all
   321  // elements in both sets can be correlated, making this function safe to use
   322  // with sets that may contain unknown values as long as the unknown case is
   323  // addressed in some reasonable way in the callback function.
   324  //
   325  // The callback always recieves values from set a as its first argument and
   326  // values from set b in its second argument, so it is safe to use with
   327  // non-commutative functions.
   328  //
   329  // As with assertValueCompatible, we assume that the target audience of error
   330  // messages here is a provider developer (via a bug report from a user) and so
   331  // we intentionally violate our usual rule of keeping cty implementation
   332  // details out of error messages.
   333  func assertSetValuesCompatible(planned, actual cty.Value, path cty.Path, f func(aVal, bVal cty.Value) bool) []error {
   334  	a := planned
   335  	b := actual
   336  
   337  	// Our methodology here is a little tricky, to deal with the fact that
   338  	// it's impossible to directly correlate two non-equal set elements because
   339  	// they don't have identities separate from their values.
   340  	// The approach is to count the number of equivalent elements each element
   341  	// of a has in b and vice-versa, and then return true only if each element
   342  	// in both sets has at least one equivalent.
   343  	as := a.AsValueSlice()
   344  	bs := b.AsValueSlice()
   345  	aeqs := make([]bool, len(as))
   346  	beqs := make([]bool, len(bs))
   347  	for ai, av := range as {
   348  		for bi, bv := range bs {
   349  			if f(av, bv) {
   350  				aeqs[ai] = true
   351  				beqs[bi] = true
   352  			}
   353  		}
   354  	}
   355  
   356  	var errs []error
   357  	for i, eq := range aeqs {
   358  		if !eq {
   359  			errs = append(errs, path.NewErrorf("planned set element %#v does not correlate with any element in actual", as[i]))
   360  		}
   361  	}
   362  	if len(errs) > 0 {
   363  		// Exit early since otherwise we're likely to generate duplicate
   364  		// error messages from the other perspective in the subsequent loop.
   365  		return errs
   366  	}
   367  	for i, eq := range beqs {
   368  		if !eq {
   369  			errs = append(errs, path.NewErrorf("actual set element %#v does not correlate with any element in plan", bs[i]))
   370  		}
   371  	}
   372  	return errs
   373  }