diff --git a/internal/configs/configschema/validate_traversal.go b/internal/configs/configschema/validate_traversal.go index c98e3f7e152e..8320c9de3663 100644 --- a/internal/configs/configschema/validate_traversal.go +++ b/internal/configs/configschema/validate_traversal.go @@ -74,14 +74,34 @@ func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnos } if attrS, exists := b.Attributes[name]; exists { + // Check for Deprecated status of this attribute. + // We currently can't provide the user with any useful guidance because + // the deprecation string is not part of the schema, but we can at + // least warn them. + // + // This purposely does not attempt to recurse into nested attribute + // types. Because nested attribute values are often not accessed via a + // direct traversal to the leaf attributes, we cannot reliably detect + // if a nested, deprecated attribute value is actually used from the + // traversal alone. More precise detection of deprecated attributes + // would require adding metadata like marks to the cty value itself, to + // be caught during evaluation. + if attrS.Deprecated { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: `Deprecated attribute`, + Detail: fmt.Sprintf(`The attribute %q is deprecated. Refer to the provider documentation for details.`, name), + Subject: next.SourceRange().Ptr(), + }) + } + // For attribute validation we will just apply the rest of the - // traversal to an unknown value of the attribute type and pass through - // HCL's own errors, since we don't want to replicate all of HCL's type - // checking rules here. + // traversal to an unknown value of the attribute type and pass + // through HCL's own errors, since we don't want to replicate all + // of HCL's type checking rules here. val := cty.UnknownVal(attrS.ImpliedType()) _, hclDiags := after.TraverseRel(val) - diags = diags.Append(hclDiags) - return diags + return diags.Append(hclDiags) } if blockS, exists := b.BlockTypes[name]; exists { diff --git a/internal/configs/configschema/validate_traversal_test.go b/internal/configs/configschema/validate_traversal_test.go index c49737d44350..f39914881191 100644 --- a/internal/configs/configschema/validate_traversal_test.go +++ b/internal/configs/configschema/validate_traversal_test.go @@ -10,9 +10,10 @@ import ( func TestStaticValidateTraversal(t *testing.T) { attrs := map[string]*Attribute{ - "str": {Type: cty.String, Optional: true}, - "list": {Type: cty.List(cty.String), Optional: true}, - "dyn": {Type: cty.DynamicPseudoType, Optional: true}, + "str": {Type: cty.String, Optional: true}, + "list": {Type: cty.List(cty.String), Optional: true}, + "dyn": {Type: cty.DynamicPseudoType, Optional: true}, + "deprecated": {Type: cty.String, Computed: true, Deprecated: true}, "nested_single": { Optional: true, NestedType: &Object{ @@ -220,6 +221,10 @@ func TestStaticValidateTraversal(t *testing.T) { `obj.nested_map["key"].optional`, ``, }, + { + `obj.deprecated`, + `Deprecated attribute: The attribute "deprecated" is deprecated. Refer to the provider documentation for details.`, + }, } for _, test := range tests { @@ -239,8 +244,9 @@ func TestStaticValidateTraversal(t *testing.T) { t.Errorf("unexpected error: %s", diags.Err().Error()) } } else { - if diags.HasErrors() { - if got := diags.Err().Error(); got != test.WantError { + err := diags.ErrWithWarnings() + if err != nil { + if got := err.Error(); got != test.WantError { t.Errorf("wrong error\ngot: %s\nwant: %s", got, test.WantError) } } else {