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

     1  package objchange
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/zclconf/go-cty/cty"
     7  
     8  	"github.com/hashicorp/terraform/internal/configs/configschema"
     9  )
    10  
    11  // AssertPlanValid checks checks whether a planned new state returned by a
    12  // provider's PlanResourceChange method is suitable to achieve a change
    13  // from priorState to config. It returns a slice with nonzero length if
    14  // any problems are detected. Because problems here indicate bugs in the
    15  // provider that generated the plannedState, they are written with provider
    16  // developers as an audience, rather than end-users.
    17  //
    18  // All of the given values must have the same type and must conform to the
    19  // implied type of the given schema, or this function may panic or produce
    20  // garbage results.
    21  //
    22  // During planning, a provider may only make changes to attributes that are
    23  // null (unset) in the configuration and are marked as "computed" in the
    24  // resource type schema, in order to insert any default values the provider
    25  // may know about. If the default value cannot be determined until apply time,
    26  // the provider can return an unknown value. Providers are forbidden from
    27  // planning a change that disagrees with any non-null argument in the
    28  // configuration.
    29  //
    30  // As a special exception, providers _are_ allowed to provide attribute values
    31  // conflicting with configuration if and only if the planned value exactly
    32  // matches the corresponding attribute value in the prior state. The provider
    33  // can use this to signal that the new value is functionally equivalent to
    34  // the old and thus no change is required.
    35  func AssertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value) []error {
    36  	return assertPlanValid(schema, priorState, config, plannedState, nil)
    37  }
    38  
    39  func assertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value, path cty.Path) []error {
    40  	var errs []error
    41  	if plannedState.IsNull() && !config.IsNull() {
    42  		errs = append(errs, path.NewErrorf("planned for absence but config wants existence"))
    43  		return errs
    44  	}
    45  	if config.IsNull() && !plannedState.IsNull() {
    46  		errs = append(errs, path.NewErrorf("planned for existence but config wants absence"))
    47  		return errs
    48  	}
    49  	if plannedState.IsNull() {
    50  		// No further checks possible if the planned value is null
    51  		return errs
    52  	}
    53  
    54  	impTy := schema.ImpliedType()
    55  
    56  	// verify attributes
    57  	moreErrs := assertPlannedAttrsValid(schema.Attributes, priorState, config, plannedState, path)
    58  	errs = append(errs, moreErrs...)
    59  
    60  	for name, blockS := range schema.BlockTypes {
    61  		path := append(path, cty.GetAttrStep{Name: name})
    62  		plannedV := plannedState.GetAttr(name)
    63  		configV := config.GetAttr(name)
    64  		priorV := cty.NullVal(impTy.AttributeType(name))
    65  		if !priorState.IsNull() {
    66  			priorV = priorState.GetAttr(name)
    67  		}
    68  		if plannedV.RawEquals(configV) {
    69  			// Easy path: nothing has changed at all
    70  			continue
    71  		}
    72  
    73  		if !configV.IsKnown() {
    74  			// An unknown config block represents a dynamic block where the
    75  			// for_each value is unknown, and therefor cannot be altered by the
    76  			// provider.
    77  			errs = append(errs, path.NewErrorf("planned value %#v for unknown dynamic block", plannedV))
    78  			continue
    79  		}
    80  
    81  		if !plannedV.IsKnown() {
    82  			// Only dynamic configuration can set blocks to unknown, so this is
    83  			// not allowed from the provider. This means that either the config
    84  			// and plan should match, or we have an error where the plan
    85  			// changed the config value, both of which have been checked.
    86  			errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
    87  			continue
    88  		}
    89  
    90  		switch blockS.Nesting {
    91  		case configschema.NestingSingle, configschema.NestingGroup:
    92  			moreErrs := assertPlanValid(&blockS.Block, priorV, configV, plannedV, path)
    93  			errs = append(errs, moreErrs...)
    94  		case configschema.NestingList:
    95  			// A NestingList might either be a list or a tuple, depending on
    96  			// whether there are dynamically-typed attributes inside. However,
    97  			// both support a similar-enough API that we can treat them the
    98  			// same for our purposes here.
    99  			if plannedV.IsNull() {
   100  				errs = append(errs, path.NewErrorf("attribute representing a list of nested blocks must be empty to indicate no blocks, not null"))
   101  				continue
   102  			}
   103  
   104  			if configV.IsNull() {
   105  				// Configuration cannot decode a block into a null value, but
   106  				// we could be dealing with a null returned by a legacy
   107  				// provider and inserted via ignore_changes. Fix the value in
   108  				// place so the length can still be compared.
   109  				configV = cty.ListValEmpty(configV.Type().ElementType())
   110  			}
   111  
   112  			plannedL := plannedV.LengthInt()
   113  			configL := configV.LengthInt()
   114  			if plannedL != configL {
   115  				errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
   116  				continue
   117  			}
   118  
   119  			for it := plannedV.ElementIterator(); it.Next(); {
   120  				idx, plannedEV := it.Element()
   121  				path := append(path, cty.IndexStep{Key: idx})
   122  				if !plannedEV.IsKnown() {
   123  					errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
   124  					continue
   125  				}
   126  				if !configV.HasIndex(idx).True() {
   127  					continue // should never happen since we checked the lengths above
   128  				}
   129  				configEV := configV.Index(idx)
   130  				priorEV := cty.NullVal(blockS.ImpliedType())
   131  				if !priorV.IsNull() && priorV.HasIndex(idx).True() {
   132  					priorEV = priorV.Index(idx)
   133  				}
   134  
   135  				moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path)
   136  				errs = append(errs, moreErrs...)
   137  			}
   138  		case configschema.NestingMap:
   139  			if plannedV.IsNull() {
   140  				errs = append(errs, path.NewErrorf("attribute representing a map of nested blocks must be empty to indicate no blocks, not null"))
   141  				continue
   142  			}
   143  
   144  			// A NestingMap might either be a map or an object, depending on
   145  			// whether there are dynamically-typed attributes inside, but
   146  			// that's decided statically and so all values will have the same
   147  			// kind.
   148  			if plannedV.Type().IsObjectType() {
   149  				plannedAtys := plannedV.Type().AttributeTypes()
   150  				configAtys := configV.Type().AttributeTypes()
   151  				for k := range plannedAtys {
   152  					if _, ok := configAtys[k]; !ok {
   153  						errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k))
   154  						continue
   155  					}
   156  					path := append(path, cty.GetAttrStep{Name: k})
   157  
   158  					plannedEV := plannedV.GetAttr(k)
   159  					if !plannedEV.IsKnown() {
   160  						errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
   161  						continue
   162  					}
   163  					configEV := configV.GetAttr(k)
   164  					priorEV := cty.NullVal(blockS.ImpliedType())
   165  					if !priorV.IsNull() && priorV.Type().HasAttribute(k) {
   166  						priorEV = priorV.GetAttr(k)
   167  					}
   168  					moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path)
   169  					errs = append(errs, moreErrs...)
   170  				}
   171  				for k := range configAtys {
   172  					if _, ok := plannedAtys[k]; !ok {
   173  						errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k))
   174  						continue
   175  					}
   176  				}
   177  			} else {
   178  				plannedL := plannedV.LengthInt()
   179  				configL := configV.LengthInt()
   180  				if plannedL != configL {
   181  					errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
   182  					continue
   183  				}
   184  				for it := plannedV.ElementIterator(); it.Next(); {
   185  					idx, plannedEV := it.Element()
   186  					path := append(path, cty.IndexStep{Key: idx})
   187  					if !plannedEV.IsKnown() {
   188  						errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
   189  						continue
   190  					}
   191  					k := idx.AsString()
   192  					if !configV.HasIndex(idx).True() {
   193  						errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k))
   194  						continue
   195  					}
   196  					configEV := configV.Index(idx)
   197  					priorEV := cty.NullVal(blockS.ImpliedType())
   198  					if !priorV.IsNull() && priorV.HasIndex(idx).True() {
   199  						priorEV = priorV.Index(idx)
   200  					}
   201  					moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path)
   202  					errs = append(errs, moreErrs...)
   203  				}
   204  				for it := configV.ElementIterator(); it.Next(); {
   205  					idx, _ := it.Element()
   206  					if !plannedV.HasIndex(idx).True() {
   207  						errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", idx.AsString()))
   208  						continue
   209  					}
   210  				}
   211  			}
   212  		case configschema.NestingSet:
   213  			if plannedV.IsNull() {
   214  				errs = append(errs, path.NewErrorf("attribute representing a set of nested blocks must be empty to indicate no blocks, not null"))
   215  				continue
   216  			}
   217  
   218  			// Because set elements have no identifier with which to correlate
   219  			// them, we can't robustly validate the plan for a nested block
   220  			// backed by a set, and so unfortunately we need to just trust the
   221  			// provider to do the right thing. :(
   222  			//
   223  			// (In principle we could correlate elements by matching the
   224  			// subset of attributes explicitly set in config, except for the
   225  			// special diff suppression rule which allows for there to be a
   226  			// planned value that is constructed by mixing part of a prior
   227  			// value with part of a config value, creating an entirely new
   228  			// element that is not present in either prior nor config.)
   229  			for it := plannedV.ElementIterator(); it.Next(); {
   230  				idx, plannedEV := it.Element()
   231  				path := append(path, cty.IndexStep{Key: idx})
   232  				if !plannedEV.IsKnown() {
   233  					errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
   234  					continue
   235  				}
   236  			}
   237  
   238  		default:
   239  			panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting))
   240  		}
   241  	}
   242  
   243  	return errs
   244  }
   245  
   246  func assertPlannedAttrsValid(schema map[string]*configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error {
   247  	var errs []error
   248  	for name, attrS := range schema {
   249  		moreErrs := assertPlannedAttrValid(name, attrS, priorState, config, plannedState, path)
   250  		errs = append(errs, moreErrs...)
   251  	}
   252  	return errs
   253  }
   254  
   255  func assertPlannedAttrValid(name string, attrS *configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error {
   256  	plannedV := plannedState.GetAttr(name)
   257  	configV := config.GetAttr(name)
   258  	priorV := cty.NullVal(attrS.Type)
   259  	if !priorState.IsNull() {
   260  		priorV = priorState.GetAttr(name)
   261  	}
   262  	path = append(path, cty.GetAttrStep{Name: name})
   263  
   264  	return assertPlannedValueValid(attrS, priorV, configV, plannedV, path)
   265  }
   266  
   267  func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, plannedV cty.Value, path cty.Path) []error {
   268  
   269  	var errs []error
   270  	if plannedV.RawEquals(configV) {
   271  		// This is the easy path: provider didn't change anything at all.
   272  		return errs
   273  	}
   274  	if plannedV.RawEquals(priorV) && !priorV.IsNull() && !configV.IsNull() {
   275  		// Also pretty easy: there is a prior value and the provider has
   276  		// returned it unchanged. This indicates that configV and plannedV
   277  		// are functionally equivalent and so the provider wishes to disregard
   278  		// the configuration value in favor of the prior.
   279  		return errs
   280  	}
   281  
   282  	switch {
   283  	// The provider can plan any value for a computed-only attribute. There may
   284  	// be a config value here in the case where a user used `ignore_changes` on
   285  	// a computed attribute and ignored the warning, or we failed to validate
   286  	// computed attributes in the config, but regardless it's not a plan error
   287  	// caused by the provider.
   288  	case attrS.Computed && !attrS.Optional:
   289  		return errs
   290  
   291  	// The provider is allowed to insert optional values when the config is
   292  	// null, but only if the attribute is computed.
   293  	case configV.IsNull() && attrS.Computed:
   294  		return errs
   295  
   296  	case configV.IsNull() && !plannedV.IsNull():
   297  		// if the attribute is not computed, then any planned value is incorrect
   298  		if attrS.Sensitive {
   299  			errs = append(errs, path.NewErrorf("sensitive planned value for a non-computed attribute"))
   300  		} else {
   301  			errs = append(errs, path.NewErrorf("planned value %#v for a non-computed attribute", plannedV))
   302  		}
   303  		return errs
   304  	}
   305  
   306  	// If this attribute has a NestedType, validate the nested object
   307  	if attrS.NestedType != nil {
   308  		return assertPlannedObjectValid(attrS.NestedType, priorV, configV, plannedV, path)
   309  	}
   310  
   311  	// If none of the above conditions match, the provider has made an invalid
   312  	// change to this attribute.
   313  	if priorV.IsNull() {
   314  		if attrS.Sensitive {
   315  			errs = append(errs, path.NewErrorf("sensitive planned value does not match config value"))
   316  		} else {
   317  			errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v", plannedV, configV))
   318  		}
   319  		return errs
   320  	}
   321  
   322  	if attrS.Sensitive {
   323  		errs = append(errs, path.NewErrorf("sensitive planned value does not match config value nor prior value"))
   324  	} else {
   325  		errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v nor prior value %#v", plannedV, configV, priorV))
   326  	}
   327  
   328  	return errs
   329  }
   330  
   331  func assertPlannedObjectValid(schema *configschema.Object, prior, config, planned cty.Value, path cty.Path) []error {
   332  	var errs []error
   333  
   334  	if planned.IsNull() && !config.IsNull() {
   335  		errs = append(errs, path.NewErrorf("planned for absence but config wants existence"))
   336  		return errs
   337  	}
   338  	if config.IsNull() && !planned.IsNull() {
   339  		errs = append(errs, path.NewErrorf("planned for existence but config wants absence"))
   340  		return errs
   341  	}
   342  	if planned.IsNull() {
   343  		// No further checks possible if the planned value is null
   344  		return errs
   345  	}
   346  
   347  	switch schema.Nesting {
   348  	case configschema.NestingSingle, configschema.NestingGroup:
   349  		moreErrs := assertPlannedAttrsValid(schema.Attributes, prior, config, planned, path)
   350  		errs = append(errs, moreErrs...)
   351  
   352  	case configschema.NestingList:
   353  		// A NestingList might either be a list or a tuple, depending on
   354  		// whether there are dynamically-typed attributes inside. However,
   355  		// both support a similar-enough API that we can treat them the
   356  		// same for our purposes here.
   357  
   358  		plannedL := planned.LengthInt()
   359  		configL := config.LengthInt()
   360  		if plannedL != configL {
   361  			errs = append(errs, path.NewErrorf("count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
   362  			return errs
   363  		}
   364  		for it := planned.ElementIterator(); it.Next(); {
   365  			idx, plannedEV := it.Element()
   366  			path := append(path, cty.IndexStep{Key: idx})
   367  			if !config.HasIndex(idx).True() {
   368  				continue // should never happen since we checked the lengths above
   369  			}
   370  			configEV := config.Index(idx)
   371  			priorEV := cty.NullVal(schema.ImpliedType())
   372  			if !prior.IsNull() && prior.HasIndex(idx).True() {
   373  				priorEV = prior.Index(idx)
   374  			}
   375  
   376  			moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path)
   377  			errs = append(errs, moreErrs...)
   378  		}
   379  
   380  	case configschema.NestingMap:
   381  		// A NestingMap might either be a map or an object, depending on
   382  		// whether there are dynamically-typed attributes inside, so we will
   383  		// break these down to maps to handle them both in the same manner.
   384  		plannedVals := map[string]cty.Value{}
   385  		configVals := map[string]cty.Value{}
   386  		priorVals := map[string]cty.Value{}
   387  
   388  		if !planned.IsNull() {
   389  			plannedVals = planned.AsValueMap()
   390  		}
   391  		if !config.IsNull() {
   392  			configVals = config.AsValueMap()
   393  		}
   394  		if !prior.IsNull() {
   395  			priorVals = prior.AsValueMap()
   396  		}
   397  
   398  		for k, plannedEV := range plannedVals {
   399  			configEV, ok := configVals[k]
   400  			if !ok {
   401  				errs = append(errs, path.NewErrorf("map key %q from plan is not present in config", k))
   402  				continue
   403  			}
   404  			path := append(path, cty.GetAttrStep{Name: k})
   405  
   406  			priorEV, ok := priorVals[k]
   407  			if !ok {
   408  				priorEV = cty.NullVal(schema.ImpliedType())
   409  			}
   410  			moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path)
   411  			errs = append(errs, moreErrs...)
   412  		}
   413  		for k := range configVals {
   414  			if _, ok := plannedVals[k]; !ok {
   415  				errs = append(errs, path.NewErrorf("map key %q from config is not present in plan", k))
   416  				continue
   417  			}
   418  		}
   419  
   420  	case configschema.NestingSet:
   421  		plannedL := planned.LengthInt()
   422  		configL := config.LengthInt()
   423  		if plannedL != configL {
   424  			errs = append(errs, path.NewErrorf("count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
   425  			return errs
   426  		}
   427  		// Because set elements have no identifier with which to correlate
   428  		// them, we can't robustly validate the plan for a nested object
   429  		// backed by a set, and so unfortunately we need to just trust the
   430  		// provider to do the right thing.
   431  	}
   432  
   433  	return errs
   434  }