diff --git a/command/format/diff.go b/command/format/diff.go index e80c7c637b..260c031dee 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -856,6 +856,10 @@ func ctyGetAttrMaybeNull(val cty.Value, name string) cty.Value { } func ctyCollectionValues(val cty.Value) []cty.Value { + if !val.IsKnown() { + return nil + } + ret := make([]cty.Value, 0, val.LengthInt()) for it := val.ElementIterator(); it.Next(); { _, value := it.Element() diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index 68c2e12ee9..742b84c9eb 100644 --- a/lang/funcs/collection.go +++ b/lang/funcs/collection.go @@ -58,6 +58,11 @@ var ElementFunc = function.New(&function.Spec{ // can't happen because we checked this in the Type function above return cty.DynamicVal, fmt.Errorf("invalid index: %s", err) } + + if !args[0].IsKnown() { + return cty.UnknownVal(retType), nil + } + l := args[0].LengthInt() if l == 0 { return cty.DynamicVal, fmt.Errorf("cannot use element function with an empty list") @@ -251,6 +256,10 @@ var IndexFunc = function.New(&function.Spec{ return cty.NilVal, fmt.Errorf("argument must be a list or tuple") } + if !args[0].IsKnown() { + return cty.UnknownVal(cty.Number), nil + } + if args[0].LengthInt() == 0 { // Easy path return cty.NilVal, fmt.Errorf("cannot search an empty list") } @@ -666,6 +675,9 @@ var MatchkeysFunc = function.New(&function.Spec{ return args[0].Type(), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + if !args[0].IsKnown() { + return cty.UnknownVal(cty.List(retType.ElementType())), nil + } if args[0].LengthInt() != args[1].LengthInt() { return cty.ListValEmpty(retType.ElementType()), fmt.Errorf("length of keys and values should be equal") @@ -885,14 +897,14 @@ var ValuesFunc = function.New(&function.Spec{ Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { mapVar := args[0] - if mapVar.LengthInt() == 0 { - return cty.ListValEmpty(retType.ElementType()), nil - } - if !mapVar.IsWhollyKnown() { return cty.UnknownVal(retType), nil } + if mapVar.LengthInt() == 0 { + return cty.ListValEmpty(retType.ElementType()), nil + } + keys, err := Keys(mapVar) if err != nil { return cty.NilVal, err @@ -941,7 +953,7 @@ var ZipmapFunc = function.New(&function.Spec{ keys := args[0] values := args[1] - if keys.LengthInt() == 0 { + if !keys.IsKnown() || !values.IsKnown() || keys.LengthInt() == 0 { return cty.Map(cty.DynamicPseudoType), nil } @@ -955,7 +967,7 @@ var ZipmapFunc = function.New(&function.Spec{ keys := args[0] values := args[1] - if keys.LengthInt() == 0 { + if !keys.IsKnown() || !values.IsKnown() || keys.LengthInt() == 0 { return cty.MapValEmpty(cty.DynamicPseudoType), nil } diff --git a/plans/objchange/compatible.go b/plans/objchange/compatible.go index 9298dc48e3..83b717554d 100644 --- a/plans/objchange/compatible.go +++ b/plans/objchange/compatible.go @@ -64,7 +64,7 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu return errs } case configschema.NestingList, configschema.NestingMap, configschema.NestingSet: - if plannedV.LengthInt() == 1 { + if plannedV.IsKnown() && plannedV.LengthInt() == 1 { elemVs := plannedV.AsValueSlice() if allLeafValuesUnknown(elemVs[0]) { return errs @@ -84,6 +84,10 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu // whether there are dynamically-typed attributes inside. However, // both support a similar-enough API that we can treat them the // same for our purposes here. + if !plannedV.IsKnown() { + continue + } + plannedL := plannedV.LengthInt() actualL := actualV.LengthInt() if plannedL != actualL { @@ -125,6 +129,9 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu } } } else { + if !plannedV.IsKnown() { + continue + } plannedL := plannedV.LengthInt() actualL := actualV.LengthInt() if plannedL != actualL { @@ -146,6 +153,9 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu // content is also their key, and so we have no way to correlate // them. Because of this, we simply verify that we still have the // same number of elements. + if !plannedV.IsKnown() { + continue + } plannedL := plannedV.LengthInt() actualL := actualV.LengthInt() if plannedL < actualL { @@ -241,10 +251,12 @@ func assertValueCompatible(planned, actual cty.Value, path cty.Path) []error { // so we can't correlate them properly. However, we will at least check // to ensure that the number of elements is consistent, along with // the general type-match checks we ran earlier in this function. - plannedL := planned.LengthInt() - actualL := actual.LengthInt() - if plannedL < actualL { - errs = append(errs, path.NewErrorf("length changed from %d to %d", plannedL, actualL)) + if planned.IsKnown() { + plannedL := planned.LengthInt() + actualL := actual.LengthInt() + if plannedL < actualL { + errs = append(errs, path.NewErrorf("length changed from %d to %d", plannedL, actualL)) + } } } diff --git a/terraform/resource.go b/terraform/resource.go index d1941739b7..9db0f2ab4a 100644 --- a/terraform/resource.go +++ b/terraform/resource.go @@ -276,6 +276,10 @@ func newResourceConfigShimmedComputedKeys(obj cty.Value, schema *configschema.Bl } blockVal := obj.GetAttr(typeName) + if !blockVal.IsKnown() { + continue + } + switch blockS.Nesting { case configschema.NestingSingle: keys := newResourceConfigShimmedComputedKeys(blockVal, &blockS.Block, fmt.Sprintf("%s%s.", prefix, typeName))