Skip to content

Commit 9216b43

Browse files
add marks for deprecated values
1 parent 1444c38 commit 9216b43

20 files changed

+1296
-16
lines changed

internal/command/jsonstate/state.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ func SensitiveAsBool(val cty.Value) cty.Value {
620620
func unmarkValueForMarshaling(v cty.Value) (unmarkedV cty.Value, sensitivePaths []cty.Path, err error) {
621621
val, pvms := v.UnmarkDeepWithPaths()
622622
sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
623+
_, otherMarks = marks.PathsWithMark(otherMarks, marks.Deprecation)
623624
if len(otherMarks) != 0 {
624625
return cty.NilVal, nil, fmt.Errorf(
625626
"%s: cannot serialize value marked as %#v for inclusion in a state snapshot (this is a bug in Terraform)",

internal/configs/named_values.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,14 @@ type Output struct {
345345
DependsOn []hcl.Traversal
346346
Sensitive bool
347347
Ephemeral bool
348+
Deprecated string
348349

349350
Preconditions []*CheckRule
350351

351352
DescriptionSet bool
352353
SensitiveSet bool
353354
EphemeralSet bool
355+
DeprecatedSet bool
354356

355357
DeclRange hcl.Range
356358
}
@@ -402,6 +404,12 @@ func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostic
402404
o.EphemeralSet = true
403405
}
404406

407+
if attr, exists := content.Attributes["deprecated"]; exists {
408+
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Deprecated)
409+
diags = append(diags, valDiags...)
410+
o.DeprecatedSet = true
411+
}
412+
405413
if attr, exists := content.Attributes["depends_on"]; exists {
406414
deps, depsDiags := DecodeDependsOn(attr)
407415
diags = append(diags, depsDiags...)
@@ -525,6 +533,9 @@ var outputBlockSchema = &hcl.BodySchema{
525533
{
526534
Name: "ephemeral",
527535
},
536+
{
537+
Name: "deprecated",
538+
},
528539
},
529540
Blocks: []hcl.BlockHeaderSchema{
530541
{Type: "precondition"},

internal/configs/named_values_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,30 @@ func TestVariableInvalidDefault(t *testing.T) {
4848
}
4949
}
5050
}
51+
52+
func TestOutputDeprecation(t *testing.T) {
53+
src := `
54+
output "foo" {
55+
value = "bar"
56+
deprecated = "This output is deprecated"
57+
}
58+
`
59+
60+
hclF, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
61+
if diags.HasErrors() {
62+
t.Fatal(diags.Error())
63+
}
64+
65+
b, diags := parseConfigFile(hclF.Body, nil, false, false)
66+
if diags.HasErrors() {
67+
t.Fatalf("unexpected error: %q", diags)
68+
}
69+
70+
if !b.Outputs[0].DeprecatedSet {
71+
t.Fatalf("expected output to be deprecated")
72+
}
73+
74+
if b.Outputs[0].Deprecated != "This output is deprecated" {
75+
t.Fatalf("expected output to have deprecation message")
76+
}
77+
}

internal/lang/checks.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ You can correct this by removing references to ephemeral values, or by using the
9393
return "", diags
9494
}
9595

96+
if depMarks := marks.FilterDeprecationMarks(valMarks); len(depMarks) > 0 {
97+
for _, depMark := range depMarks {
98+
diags = diags.Append(&hcl.Diagnostic{
99+
Severity: hcl.DiagWarning,
100+
Summary: "Deprecated value used",
101+
Detail: depMark.Message,
102+
Subject: expr.Range().Ptr(),
103+
})
104+
}
105+
}
106+
96107
// NOTE: We've discarded any other marks the string might have been carrying,
97108
// aside from the sensitive mark.
98109

internal/lang/marks/marks.go

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package marks
55

66
import (
7+
"github.com/hashicorp/hcl/v2"
78
"github.com/zclconf/go-cty/cty"
89
)
910

@@ -17,16 +18,30 @@ func (m valueMark) GoString() string {
1718
}
1819

1920
// Has returns true if and only if the cty.Value has the given mark.
20-
func Has(val cty.Value, mark valueMark) bool {
21-
return val.HasMark(mark)
21+
func Has(val cty.Value, mark interface{}) bool {
22+
switch m := mark.(type) {
23+
case valueMark:
24+
return val.HasMark(m)
25+
26+
// For value marks Has returns true if a mark of the type is present
27+
case DeprecationMark:
28+
for depMark := range val.Marks() {
29+
if _, ok := depMark.(DeprecationMark); ok {
30+
return true
31+
}
32+
}
33+
return false
34+
default:
35+
panic("Unknown mark type")
36+
}
2237
}
2338

2439
// Contains returns true if the cty.Value or any any value within it contains
2540
// the given mark.
26-
func Contains(val cty.Value, mark valueMark) bool {
41+
func Contains(val cty.Value, mark interface{}) bool {
2742
ret := false
2843
cty.Walk(val, func(_ cty.Path, v cty.Value) (bool, error) {
29-
if v.HasMark(mark) {
44+
if Has(v, mark) {
3045
ret = true
3146
return false, nil
3247
}
@@ -35,6 +50,33 @@ func Contains(val cty.Value, mark valueMark) bool {
3550
return ret
3651
}
3752

53+
func FilterDeprecationMarks(marks cty.ValueMarks) []DeprecationMark {
54+
depMarks := []DeprecationMark{}
55+
for mark := range marks {
56+
if d, ok := mark.(DeprecationMark); ok {
57+
depMarks = append(depMarks, d)
58+
}
59+
}
60+
return depMarks
61+
}
62+
63+
func GetDeprecationMarks(val cty.Value) []DeprecationMark {
64+
_, marks := val.UnmarkDeep()
65+
return FilterDeprecationMarks(marks)
66+
67+
}
68+
69+
func RemoveDeprecationMarks(val cty.Value) cty.Value {
70+
newVal, marks := val.Unmark()
71+
for mark := range marks {
72+
if _, ok := mark.(DeprecationMark); ok {
73+
continue
74+
}
75+
newVal = newVal.Mark(mark)
76+
}
77+
return newVal
78+
}
79+
3880
// Sensitive indicates that this value is marked as sensitive in the context of
3981
// Terraform.
4082
const Sensitive = valueMark("Sensitive")
@@ -51,3 +93,22 @@ const Ephemeral = valueMark("Ephemeral")
5193
// another value's type. This is part of the implementation of the console-only
5294
// `type` function.
5395
const TypeType = valueMark("TypeType")
96+
97+
type DeprecationMark struct {
98+
Message string
99+
Origin *hcl.Range
100+
}
101+
102+
func (d DeprecationMark) GoString() string {
103+
return "marks.deprecation<" + d.Message + ">"
104+
}
105+
106+
// Empty deprecation mark for usage in marks.Has / Contains / etc
107+
var Deprecation = NewDeprecation("", nil)
108+
109+
func NewDeprecation(message string, origin *hcl.Range) DeprecationMark {
110+
return DeprecationMark{
111+
Message: message,
112+
Origin: origin,
113+
}
114+
}

internal/lang/marks/marks_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package marks
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/hcl/v2"
10+
"github.com/zclconf/go-cty/cty"
11+
)
12+
13+
func TestDeprecationMark(t *testing.T) {
14+
deprecationWithoutRange := cty.StringVal("OldValue").Mark(NewDeprecation("This is outdated", nil))
15+
deprecationWithRange := cty.StringVal("OldValue").Mark(NewDeprecation("This is outdated", &hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 10}}))
16+
17+
composite := cty.ObjectVal(map[string]cty.Value{
18+
"foo": deprecationWithRange,
19+
"bar": deprecationWithoutRange,
20+
"baz": cty.StringVal("Not deprecated"),
21+
})
22+
23+
if !deprecationWithRange.IsMarked() {
24+
t.Errorf("Expected deprecationWithRange to be marked")
25+
}
26+
if !deprecationWithoutRange.IsMarked() {
27+
t.Errorf("Expected deprecationWithoutRange to be marked")
28+
}
29+
if composite.IsMarked() {
30+
t.Errorf("Expected composite to be marked")
31+
}
32+
33+
if !Has(deprecationWithRange, Deprecation) {
34+
t.Errorf("Expected deprecationWithRange to be marked with Deprecation")
35+
}
36+
if !Has(deprecationWithoutRange, Deprecation) {
37+
t.Errorf("Expected deprecationWithoutRange to be marked with Deprecation")
38+
}
39+
if Has(composite, Deprecation) {
40+
t.Errorf("Expected composite to be marked with Deprecation")
41+
}
42+
43+
if !Contains(deprecationWithRange, Deprecation) {
44+
t.Errorf("Expected deprecationWithRange to be contain Deprecation Mark")
45+
}
46+
if !Contains(deprecationWithoutRange, Deprecation) {
47+
t.Errorf("Expected deprecationWithoutRange to be contain Deprecation Mark")
48+
}
49+
if !Contains(composite, Deprecation) {
50+
t.Errorf("Expected composite to be contain Deprecation Mark")
51+
}
52+
}

internal/lang/marks/paths.go

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package marks
55

66
import (
7+
"fmt"
78
"sort"
89
"strings"
910

@@ -28,16 +29,36 @@ func PathsWithMark(pvms []cty.PathValueMarks, wantMark any) (withWanted []cty.Pa
2829
}
2930

3031
for _, pvm := range pvms {
31-
if _, ok := pvm.Marks[wantMark]; ok {
32+
pathHasMark := false
33+
pathHasOtherMarks := false
34+
for mark := range pvm.Marks {
35+
switch wantMark.(type) {
36+
case valueMark, string:
37+
if mark == wantMark {
38+
pathHasMark = true
39+
} else {
40+
pathHasOtherMarks = true
41+
}
42+
43+
// For data marks we check if a mark of the type exists
44+
case DeprecationMark:
45+
if _, ok := mark.(DeprecationMark); ok {
46+
pathHasMark = true
47+
} else {
48+
pathHasOtherMarks = true
49+
}
50+
51+
default:
52+
panic(fmt.Sprintf("unexpected mark type %T", wantMark))
53+
}
54+
}
55+
56+
if pathHasMark {
3257
withWanted = append(withWanted, pvm.Path)
3358
}
3459

35-
for mark := range pvm.Marks {
36-
if mark != wantMark {
37-
withOthers = append(withOthers, pvm)
38-
// only add a path with unwanted marks a single time
39-
break
40-
}
60+
if pathHasOtherMarks {
61+
withOthers = append(withOthers, pvm)
4162
}
4263
}
4364

@@ -57,7 +78,21 @@ func RemoveAll(pvms []cty.PathValueMarks, remove any) []cty.PathValueMarks {
5778
var res []cty.PathValueMarks
5879

5980
for _, pvm := range pvms {
60-
delete(pvm.Marks, remove)
81+
switch remove.(type) {
82+
case valueMark, string:
83+
delete(pvm.Marks, remove)
84+
85+
case DeprecationMark:
86+
// We want to delete all marks of this type
87+
for mark := range pvm.Marks {
88+
if _, ok := mark.(DeprecationMark); ok {
89+
delete(pvm.Marks, mark)
90+
}
91+
}
92+
93+
default:
94+
panic(fmt.Sprintf("unexpected mark type %T", remove))
95+
}
6196
if len(pvm.Marks) > 0 {
6297
res = append(res, pvm)
6398
}

0 commit comments

Comments
 (0)