github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/parser/funcs/defaults.go (about)

     1  // Copied from github.com/hashicorp/terraform/internal/lang/funcs
     2  package funcs
     3  
     4  import (
     5  	"fmt"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  	"github.com/zclconf/go-cty/cty/convert"
     9  	"github.com/zclconf/go-cty/cty/function"
    10  )
    11  
    12  // DefaultsFunc is a helper function for substituting default values in
    13  // place of null values in a given data structure.
    14  //
    15  // See the documentation for function Defaults for more information.
    16  var DefaultsFunc = function.New(&function.Spec{
    17  	Params: []function.Parameter{
    18  		{
    19  			Name:        "input",
    20  			Type:        cty.DynamicPseudoType,
    21  			AllowNull:   true,
    22  			AllowMarked: true,
    23  		},
    24  		{
    25  			Name:        "defaults",
    26  			Type:        cty.DynamicPseudoType,
    27  			AllowMarked: true,
    28  		},
    29  	},
    30  	Type: func(args []cty.Value) (cty.Type, error) {
    31  		// The result type is guaranteed to be the same as the input type,
    32  		// since all we're doing is replacing null values with non-null
    33  		// values of the same type.
    34  		retType := args[0].Type()
    35  		defaultsType := args[1].Type()
    36  
    37  		// This function is aimed at filling in object types or collections
    38  		// of object types where some of the attributes might be null, so
    39  		// it doesn't make sense to use a primitive type directly with it.
    40  		// (The "coalesce" function may be appropriate for such cases.)
    41  		if retType.IsPrimitiveType() {
    42  			// This error message is a bit of a fib because we can actually
    43  			// apply defaults to tuples too, but we expect that to be so
    44  			// unusual as to not be worth mentioning here, because mentioning
    45  			// it would require using some less-well-known Terraform language
    46  			// terminology in the message (tuple types, structural types).
    47  			return cty.DynamicPseudoType, function.NewArgErrorf(1, "only object types and collections of object types can have defaults applied")
    48  		}
    49  
    50  		defaultsPath := make(cty.Path, 0, 4) // some capacity so that most structures won't reallocate
    51  		if err := defaultsAssertSuitableFallback(retType, defaultsType, defaultsPath); err != nil {
    52  			errMsg := err.Error()
    53  			return cty.DynamicPseudoType, function.NewArgErrorf(1, "%s", errMsg)
    54  		}
    55  
    56  		return retType, nil
    57  	},
    58  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
    59  		if args[0].Type().HasDynamicTypes() {
    60  			// If the types our input object aren't known yet for some reason
    61  			// then we'll defer all of our work here, because our
    62  			// interpretation of the defaults depends on the types in
    63  			// the input.
    64  			return cty.UnknownVal(retType), nil
    65  		}
    66  
    67  		v := defaultsApply(args[0], args[1])
    68  		return v, nil
    69  	},
    70  })
    71  
    72  func defaultsApply(input, fallback cty.Value) cty.Value {
    73  	wantTy := input.Type()
    74  
    75  	umInput, inputMarks := input.Unmark()
    76  	umFb, fallbackMarks := fallback.Unmark()
    77  
    78  	// If neither are known, we very conservatively return an unknown value
    79  	// with the union of marks on both input and default.
    80  	if !(umInput.IsKnown() && umFb.IsKnown()) {
    81  		return cty.UnknownVal(wantTy).WithMarks(inputMarks).WithMarks(fallbackMarks)
    82  	}
    83  
    84  	// For the rest of this function we're assuming that the given defaults
    85  	// will always be valid, because we expect to have caught any problems
    86  	// during the type checking phase. Any inconsistencies that reach here are
    87  	// therefore considered to be implementation bugs, and so will panic.
    88  
    89  	// Our strategy depends on the kind of type we're working with.
    90  	switch {
    91  	case wantTy.IsPrimitiveType():
    92  		// For leaf primitive values the rule is relatively simple: use the
    93  		// input if it's non-null, or fallback if input is null.
    94  		if !umInput.IsNull() {
    95  			return input
    96  		}
    97  		v, err := convert.Convert(umFb, wantTy)
    98  		if err != nil {
    99  			// Should not happen because we checked in defaultsAssertSuitableFallback
   100  			panic(err.Error())
   101  		}
   102  		return v.WithMarks(fallbackMarks)
   103  
   104  	case wantTy.IsObjectType():
   105  		// For structural types, a null input value must be passed through. We
   106  		// do not apply default values for missing optional structural values,
   107  		// only their contents.
   108  		//
   109  		// We also pass through the input if the fallback value is null. This
   110  		// can happen if the given defaults do not include a value for this
   111  		// attribute.
   112  		if umInput.IsNull() || umFb.IsNull() {
   113  			return input
   114  		}
   115  		atys := wantTy.AttributeTypes()
   116  		ret := map[string]cty.Value{}
   117  		for attr, aty := range atys {
   118  			inputSub := umInput.GetAttr(attr)
   119  			fallbackSub := cty.NullVal(aty)
   120  			if umFb.Type().HasAttribute(attr) {
   121  				fallbackSub = umFb.GetAttr(attr)
   122  			}
   123  			ret[attr] = defaultsApply(inputSub.WithMarks(inputMarks), fallbackSub.WithMarks(fallbackMarks))
   124  		}
   125  		return cty.ObjectVal(ret)
   126  
   127  	case wantTy.IsTupleType():
   128  		// For structural types, a null input value must be passed through. We
   129  		// do not apply default values for missing optional structural values,
   130  		// only their contents.
   131  		//
   132  		// We also pass through the input if the fallback value is null. This
   133  		// can happen if the given defaults do not include a value for this
   134  		// attribute.
   135  		if umInput.IsNull() || umFb.IsNull() {
   136  			return input
   137  		}
   138  
   139  		l := wantTy.Length()
   140  		ret := make([]cty.Value, l)
   141  		for i := 0; i < l; i++ {
   142  			inputSub := umInput.Index(cty.NumberIntVal(int64(i)))
   143  			fallbackSub := umFb.Index(cty.NumberIntVal(int64(i)))
   144  			ret[i] = defaultsApply(inputSub.WithMarks(inputMarks), fallbackSub.WithMarks(fallbackMarks))
   145  		}
   146  		return cty.TupleVal(ret)
   147  
   148  	case wantTy.IsCollectionType():
   149  		// For collection types we apply a single fallback value to each
   150  		// element of the input collection, because in the situations this
   151  		// function is intended for we assume that the number of elements
   152  		// is the caller's decision, and so we'll just apply the same defaults
   153  		// to all of the elements.
   154  		ety := wantTy.ElementType()
   155  		switch {
   156  		case wantTy.IsMapType():
   157  			newVals := map[string]cty.Value{}
   158  
   159  			if !umInput.IsNull() {
   160  				for it := umInput.ElementIterator(); it.Next(); {
   161  					k, v := it.Element()
   162  					newVals[k.AsString()] = defaultsApply(v.WithMarks(inputMarks), fallback.WithMarks(fallbackMarks))
   163  				}
   164  			}
   165  
   166  			if len(newVals) == 0 {
   167  				return cty.MapValEmpty(ety)
   168  			}
   169  			return cty.MapVal(newVals)
   170  		case wantTy.IsListType(), wantTy.IsSetType():
   171  			var newVals []cty.Value
   172  
   173  			if !umInput.IsNull() {
   174  				for it := umInput.ElementIterator(); it.Next(); {
   175  					_, v := it.Element()
   176  					newV := defaultsApply(v.WithMarks(inputMarks), fallback.WithMarks(fallbackMarks))
   177  					newVals = append(newVals, newV)
   178  				}
   179  			}
   180  
   181  			if len(newVals) == 0 {
   182  				if wantTy.IsSetType() {
   183  					return cty.SetValEmpty(ety)
   184  				}
   185  				return cty.ListValEmpty(ety)
   186  			}
   187  			if wantTy.IsSetType() {
   188  				return cty.SetVal(newVals)
   189  			}
   190  			return cty.ListVal(newVals)
   191  		default:
   192  			// There are no other collection types, so this should not happen
   193  			panic(fmt.Sprintf("invalid collection type %#v", wantTy))
   194  		}
   195  	default:
   196  		// We should've caught anything else in defaultsAssertSuitableFallback,
   197  		// so this should not happen.
   198  		panic(fmt.Sprintf("invalid target type %#v", wantTy))
   199  	}
   200  }
   201  
   202  func defaultsAssertSuitableFallback(wantTy, fallbackTy cty.Type, fallbackPath cty.Path) error {
   203  	// If the type we want is a collection type then we need to keep peeling
   204  	// away collection type wrappers until we find the non-collection-type
   205  	// that's underneath, which is what the fallback will actually be applied
   206  	// to.
   207  	inCollection := false
   208  	for wantTy.IsCollectionType() {
   209  		wantTy = wantTy.ElementType()
   210  		inCollection = true
   211  	}
   212  
   213  	switch {
   214  	case wantTy.IsPrimitiveType():
   215  		// The fallback is valid if it's equal to or convertible to what we want.
   216  		if fallbackTy.Equals(wantTy) {
   217  			return nil
   218  		}
   219  		conversion := convert.GetConversion(fallbackTy, wantTy)
   220  		if conversion == nil {
   221  			msg := convert.MismatchMessage(fallbackTy, wantTy)
   222  			return fallbackPath.NewErrorf("invalid default value for %s: %s", wantTy.FriendlyName(), msg)
   223  		}
   224  		return nil
   225  	case wantTy.IsObjectType():
   226  		if !fallbackTy.IsObjectType() {
   227  			if inCollection {
   228  				return fallbackPath.NewErrorf("the default value for a collection of an object type must itself be an object type, not %s", fallbackTy.FriendlyName())
   229  			}
   230  			return fallbackPath.NewErrorf("the default value for an object type must itself be an object type, not %s", fallbackTy.FriendlyName())
   231  		}
   232  		for attr, wantAty := range wantTy.AttributeTypes() {
   233  			if !fallbackTy.HasAttribute(attr) {
   234  				continue // it's always okay to not have a default value
   235  			}
   236  			fallbackSubpath := fallbackPath.GetAttr(attr)
   237  			fallbackSubTy := fallbackTy.AttributeType(attr)
   238  			err := defaultsAssertSuitableFallback(wantAty, fallbackSubTy, fallbackSubpath)
   239  			if err != nil {
   240  				return err
   241  			}
   242  		}
   243  		for attr := range fallbackTy.AttributeTypes() {
   244  			if !wantTy.HasAttribute(attr) {
   245  				fallbackSubpath := fallbackPath.GetAttr(attr)
   246  				return fallbackSubpath.NewErrorf("target type does not expect an attribute named %q", attr)
   247  			}
   248  		}
   249  		return nil
   250  	case wantTy.IsTupleType():
   251  		if !fallbackTy.IsTupleType() {
   252  			if inCollection {
   253  				return fallbackPath.NewErrorf("the default value for a collection of a tuple type must itself be a tuple type, not %s", fallbackTy.FriendlyName())
   254  			}
   255  			return fallbackPath.NewErrorf("the default value for a tuple type must itself be a tuple type, not %s", fallbackTy.FriendlyName())
   256  		}
   257  		wantEtys := wantTy.TupleElementTypes()
   258  		fallbackEtys := fallbackTy.TupleElementTypes()
   259  		if got, want := len(wantEtys), len(fallbackEtys); got != want {
   260  			return fallbackPath.NewErrorf("the default value for a tuple type of length %d must also have length %d, not %d", want, want, got)
   261  		}
   262  		for i := 0; i < len(wantEtys); i++ {
   263  			fallbackSubpath := fallbackPath.IndexInt(i)
   264  			wantSubTy := wantEtys[i]
   265  			fallbackSubTy := fallbackEtys[i]
   266  			err := defaultsAssertSuitableFallback(wantSubTy, fallbackSubTy, fallbackSubpath)
   267  			if err != nil {
   268  				return err
   269  			}
   270  		}
   271  		return nil
   272  	default:
   273  		// No other types are supported right now.
   274  		return fallbackPath.NewErrorf("cannot apply defaults to %s", wantTy.FriendlyName())
   275  	}
   276  }
   277  
   278  // Defaults is a helper function for substituting default values in
   279  // place of null values in a given data structure.
   280  //
   281  // This is primarily intended for use with a module input variable that
   282  // has an object type constraint (or a collection thereof) that has optional
   283  // attributes, so that the receiver of a value that omits those attributes
   284  // can insert non-null default values in place of the null values caused by
   285  // omitting the attributes.
   286  func Defaults(input, defaults cty.Value) (cty.Value, error) {
   287  	return DefaultsFunc.Call([]cty.Value{input, defaults})
   288  }