Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hashicorp/terraform
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.6
Choose a base ref
...
head repository: hashicorp/terraform
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.0.7
Choose a head ref
  • 15 commits
  • 22 files changed
  • 2 contributors

Commits on Sep 3, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    AVVS Vitaly Aminev
    Copy the full SHA
    dc17eb4 View commit details

Commits on Sep 10, 2021

  1. backport of commit 782132d

    jbardin committed Sep 10, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    AVVS Vitaly Aminev
    Copy the full SHA
    a29a5eb View commit details
  2. Merge pull request #29564 from hashicorp/backport/jbardin/optional-co…

    …mputed-nested-objects/radically-hardy-lionfish
    
    Backport of remove incorrect computed check into v1.0
    jbardin authored Sep 10, 2021
    Copy the full SHA
    94a5916 View commit details

Commits on Sep 13, 2021

  1. configschema: do not expose optional attributes

    Objects with optional attributes are only used for the decoding of
    HCL, and those types should never be exposed elsewhere within
    terraform.
    
    Separate the external ImpliedType method from the cty.Type generated
    internally for the decoder spec. This unfortunately causes our
    ImpliedType method to return a different type than the
    hcldec.ImpliedType function, but the former is only used within
    terraform for concrete values, while the latter is used to decode
    HCL. Renaming the ImpliedType methods could be done to further
    differentiate them, but that does cause fairly large diff in the
    codebase that does not seem worth the effort at this time.
    jbardin committed Sep 13, 2021
    Copy the full SHA
    ee46dc6 View commit details
  2. Copy the full SHA
    3cdab10 View commit details
  3. configs: add ConstraintType to config.Variable

    In order to handle optional attributes, the Variable type needs to keep
    track of the type constraint for decoding and conversion, as well as the
    concrete type for creating values and type comparison.
    
    Since the Type field is referenced throughout the codebase, and for
    future refactoring if the handling of optional attributes changes
    significantly, the constraint is now loaded into an entirely new field
    called ConstraintType. This prevents types containing
    ObjectWithOptionalAttrs from escaping the decode/conversion codepaths
    into the rest of the codebase.
    jbardin committed Sep 13, 2021
    Copy the full SHA
    d95d746 View commit details
  4. Merge pull request #29570 from hashicorp/jbardin/backport-29559

    v1.0 backport 29559
    jbardin authored Sep 13, 2021
    Copy the full SHA
    fca505a View commit details
  5. update CHANGELOG.md

    jbardin committed Sep 13, 2021
    Copy the full SHA
    7e64ff8 View commit details

Commits on Sep 14, 2021

  1. update CHANGELOG.md

    jbardin committed Sep 14, 2021
    Copy the full SHA
    47c5289 View commit details
  2. backport of commit 9029870

    jbardin committed Sep 14, 2021
    Copy the full SHA
    37c5a26 View commit details

Commits on Sep 15, 2021

  1. backport of commit 331dc8b

    jbardin committed Sep 15, 2021
    Copy the full SHA
    801b9c5 View commit details
  2. Merge pull request #29587 from hashicorp/backport/jbardin/proposed-ne…

    …w-empty-containers/actually-working-viper
    
    Backport of handle empty containers in ProposedNew NestedTypes into v1.0
    jbardin authored Sep 15, 2021
    Copy the full SHA
    7d0a67d View commit details
  3. update CHANGELOG.md

    jbardin committed Sep 15, 2021
    Copy the full SHA
    88baf11 View commit details
  4. udpate CHANGELOG.md

    jbardin committed Sep 15, 2021
    Copy the full SHA
    df30f76 View commit details
  5. Release v1.0.7

    hc-github-team-tf-core committed Sep 15, 2021
    Copy the full SHA
    69361e8 View commit details
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.0.7 (September 15, 2021)

BUG FIXES:

* core: Remove check for computed attributes which is no longer valid with optional structural attributes ([#29563](https://github.com/hashicorp/terraform/issues/29563))
* core: Prevent object types with optional attributes from being instantiated as concrete values, which can lead to failures in type comparison ([#29559](https://github.com/hashicorp/terraform/issues/29559))
* core: Empty containers in the configuration were not planned correctly when used with optional structural attributes ([#29580](https://github.com/hashicorp/terraform/issues/29580))

## 1.0.6 (September 03, 2021)

ENHANCEMENTS:
23 changes: 13 additions & 10 deletions internal/backend/unparsed_value_test.go
Original file line number Diff line number Diff line change
@@ -24,30 +24,33 @@ func TestParseVariableValuesUndeclared(t *testing.T) {
}
decls := map[string]*configs.Variable{
"declared1": {
Name: "declared1",
Type: cty.String,
ParsingMode: configs.VariableParseLiteral,
Name: "declared1",
Type: cty.String,
ConstraintType: cty.String,
ParsingMode: configs.VariableParseLiteral,
DeclRange: hcl.Range{
Filename: "fake.tf",
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
},
},
"missing1": {
Name: "missing1",
Type: cty.String,
ParsingMode: configs.VariableParseLiteral,
Name: "missing1",
Type: cty.String,
ConstraintType: cty.String,
ParsingMode: configs.VariableParseLiteral,
DeclRange: hcl.Range{
Filename: "fake.tf",
Start: hcl.Pos{Line: 3, Column: 1, Byte: 0},
End: hcl.Pos{Line: 3, Column: 1, Byte: 0},
},
},
"missing2": {
Name: "missing1",
Type: cty.String,
ParsingMode: configs.VariableParseLiteral,
Default: cty.StringVal("default for missing2"),
Name: "missing1",
Type: cty.String,
ConstraintType: cty.String,
ParsingMode: configs.VariableParseLiteral,
Default: cty.StringVal("default for missing2"),
DeclRange: hcl.Range{
Filename: "fake.tf",
Start: hcl.Pos{Line: 4, Column: 1, Byte: 0},
48 changes: 23 additions & 25 deletions internal/configs/configschema/coerce_value.go
Original file line number Diff line number Diff line change
@@ -27,16 +27,19 @@ func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
}

func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
convType := b.specType()
impliedType := convType.WithoutOptionalAttributesDeep()

switch {
case in.IsNull():
return cty.NullVal(b.ImpliedType()), nil
return cty.NullVal(impliedType), nil
case !in.IsKnown():
return cty.UnknownVal(b.ImpliedType()), nil
return cty.UnknownVal(impliedType), nil
}

ty := in.Type()
if !ty.IsObjectType() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
return cty.UnknownVal(impliedType), path.NewErrorf("an object is required")
}

for name := range ty.AttributeTypes() {
@@ -46,29 +49,32 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
if _, defined := b.BlockTypes[name]; defined {
continue
}
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
return cty.UnknownVal(impliedType), path.NewErrorf("unexpected attribute %q", name)
}

attrs := make(map[string]cty.Value)

for name, attrS := range b.Attributes {
attrType := impliedType.AttributeType(name)
attrConvType := convType.AttributeType(name)

var val cty.Value
switch {
case ty.HasAttribute(name):
val = in.GetAttr(name)
case attrS.Computed || attrS.Optional:
val = cty.NullVal(attrS.Type)
val = cty.NullVal(attrType)
default:
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
return cty.UnknownVal(impliedType), path.NewErrorf("attribute %q is required", name)
}

val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
val, err := convert.Convert(val, attrConvType)
if err != nil {
return cty.UnknownVal(b.ImpliedType()), err
return cty.UnknownVal(impliedType), append(path, cty.GetAttrStep{Name: name}).NewError(err)
}

attrs[name] = val
}

for typeName, blockS := range b.BlockTypes {
switch blockS.Nesting {

@@ -79,7 +85,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
val := in.GetAttr(typeName)
attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
if err != nil {
return cty.UnknownVal(b.ImpliedType()), err
return cty.UnknownVal(impliedType), err
}
default:
attrs[typeName] = blockS.EmptyValue()
@@ -100,7 +106,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
}

if !coll.CanIterateElements() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
return cty.UnknownVal(impliedType), path.NewErrorf("must be a list")
}
l := coll.LengthInt()

@@ -116,7 +122,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
idx, val := it.Element()
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
if err != nil {
return cty.UnknownVal(b.ImpliedType()), err
return cty.UnknownVal(impliedType), err
}
elems = append(elems, val)
}
@@ -141,7 +147,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
}

if !coll.CanIterateElements() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
return cty.UnknownVal(impliedType), path.NewErrorf("must be a set")
}
l := coll.LengthInt()

@@ -157,7 +163,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
idx, val := it.Element()
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
if err != nil {
return cty.UnknownVal(b.ImpliedType()), err
return cty.UnknownVal(impliedType), err
}
elems = append(elems, val)
}
@@ -182,7 +188,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
}

if !coll.CanIterateElements() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
}
l := coll.LengthInt()
if l == 0 {
@@ -196,11 +202,11 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
var err error
key, val := it.Element()
if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
}
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
if err != nil {
return cty.UnknownVal(b.ImpliedType()), err
return cty.UnknownVal(impliedType), err
}
elems[key.AsString()] = val
}
@@ -240,11 +246,3 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {

return cty.ObjectVal(attrs), nil
}

func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
val, err := convert.Convert(in, a.Type)
if err != nil {
return cty.UnknownVal(a.Type), path.NewError(err)
}
return val, nil
}
61 changes: 61 additions & 0 deletions internal/configs/configschema/coerce_value_test.go
Original file line number Diff line number Diff line change
@@ -538,6 +538,67 @@ func TestCoerceValue(t *testing.T) {
}),
``,
},
"nested types": {
// handle NestedTypes
&Block{
Attributes: map[string]*Attribute{
"foo": {
NestedType: &Object{
Nesting: NestingList,
Attributes: map[string]*Attribute{
"bar": {
Type: cty.String,
Required: true,
},
"baz": {
Type: cty.Map(cty.String),
Optional: true,
},
},
},
Optional: true,
},
"fob": {
NestedType: &Object{
Nesting: NestingSet,
Attributes: map[string]*Attribute{
"bar": {
Type: cty.String,
Optional: true,
},
},
},
Optional: true,
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
"baz": cty.NullVal(cty.Map(cty.String)),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.NullVal(cty.Map(cty.String)),
}),
}),
"fob": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
"bar": cty.String,
}))),
}),
``,
},
}

for name, test := range tests {
6 changes: 3 additions & 3 deletions internal/configs/configschema/decoder_spec.go
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
// implied type more complete, but if there are any
// dynamically-typed attributes inside we must use a tuple
// instead, at the expense of our type then not being predictable.
if blockS.Block.ImpliedType().HasDynamicTypes() {
if blockS.Block.specType().HasDynamicTypes() {
ret[name] = &hcldec.BlockTupleSpec{
TypeName: name,
Nested: childSpec,
@@ -153,7 +153,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
// implied type more complete, but if there are any
// dynamically-typed attributes inside we must use a tuple
// instead, at the expense of our type then not being predictable.
if blockS.Block.ImpliedType().HasDynamicTypes() {
if blockS.Block.specType().HasDynamicTypes() {
ret[name] = &hcldec.BlockObjectSpec{
TypeName: name,
Nested: childSpec,
@@ -191,7 +191,7 @@ func (a *Attribute) decoderSpec(name string) hcldec.Spec {
panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.")
}

ty := a.NestedType.ImpliedType()
ty := a.NestedType.specType()
ret.Type = ty
ret.Required = a.Required || a.NestedType.MinItems > 0
return ret
24 changes: 21 additions & 3 deletions internal/configs/configschema/implied_type.go
Original file line number Diff line number Diff line change
@@ -8,11 +8,23 @@ import (
// ImpliedType returns the cty.Type that would result from decoding a
// configuration block using the receiving block schema.
//
// The type returned from Block.ImpliedType differs from the type returned by
// hcldec.ImpliedType in that there will be no objects with optional
// attributes, since this value is not to be used for the decoding of
// configuration.
//
// ImpliedType always returns a result, even if the given schema is
// inconsistent. Code that creates configschema.Block objects should be
// tested using the InternalValidate method to detect any inconsistencies
// that would cause this method to fall back on defaults and assumptions.
func (b *Block) ImpliedType() cty.Type {
return b.specType().WithoutOptionalAttributesDeep()
}

// specType returns the cty.Type used for decoding a configuration
// block using the receiving block schema. This is the type used internally by
// hcldec to decode configuration.
func (b *Block) specType() cty.Type {
if b == nil {
return cty.EmptyObject
}
@@ -41,22 +53,28 @@ func (b *Block) ContainsSensitive() bool {
return false
}

// ImpliedType returns the cty.Type that would result from decoding a NestedType
// Attribute using the receiving block schema.
// ImpliedType returns the cty.Type that would result from decoding a
// NestedType Attribute using the receiving block schema.
//
// ImpliedType always returns a result, even if the given schema is
// inconsistent. Code that creates configschema.Object objects should be tested
// using the InternalValidate method to detect any inconsistencies that would
// cause this method to fall back on defaults and assumptions.
func (o *Object) ImpliedType() cty.Type {
return o.specType().WithoutOptionalAttributesDeep()
}

// specType returns the cty.Type used for decoding a NestedType Attribute using
// the receiving block schema.
func (o *Object) specType() cty.Type {
if o == nil {
return cty.EmptyObject
}

attrTys := make(map[string]cty.Type, len(o.Attributes))
for name, attrS := range o.Attributes {
if attrS.NestedType != nil {
attrTys[name] = attrS.NestedType.ImpliedType()
attrTys[name] = attrS.NestedType.specType()
} else {
attrTys[name] = attrS.Type
}
Loading