mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
add deprecation marks
This commit is contained in:
parent
8a615f93e9
commit
d813ad14f7
8 changed files with 321 additions and 13 deletions
|
|
@ -620,6 +620,7 @@ func SensitiveAsBool(val cty.Value) cty.Value {
|
|||
func unmarkValueForMarshaling(v cty.Value) (unmarkedV cty.Value, sensitivePaths []cty.Path, err error) {
|
||||
val, pvms := v.UnmarkDeepWithPaths()
|
||||
sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
|
||||
_, otherMarks = marks.PathsWithMark(otherMarks, marks.Deprecation)
|
||||
if len(otherMarks) != 0 {
|
||||
return cty.NilVal, nil, fmt.Errorf(
|
||||
"%s: cannot serialize value marked as %#v for inclusion in a state snapshot (this is a bug in Terraform)",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package marks
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
|
|
@ -17,16 +18,30 @@ func (m valueMark) GoString() string {
|
|||
}
|
||||
|
||||
// Has returns true if and only if the cty.Value has the given mark.
|
||||
func Has(val cty.Value, mark valueMark) bool {
|
||||
return val.HasMark(mark)
|
||||
func Has(val cty.Value, mark interface{}) bool {
|
||||
switch m := mark.(type) {
|
||||
case valueMark:
|
||||
return val.HasMark(m)
|
||||
|
||||
// For value marks Has returns true if a mark of the type is present
|
||||
case DeprecationMark:
|
||||
for depMark := range val.Marks() {
|
||||
if _, ok := depMark.(DeprecationMark); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
panic("Unknown mark type")
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the cty.Value or any any value within it contains
|
||||
// the given mark.
|
||||
func Contains(val cty.Value, mark valueMark) bool {
|
||||
func Contains(val cty.Value, mark interface{}) bool {
|
||||
ret := false
|
||||
cty.Walk(val, func(_ cty.Path, v cty.Value) (bool, error) {
|
||||
if v.HasMark(mark) {
|
||||
if Has(v, mark) {
|
||||
ret = true
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -35,6 +50,33 @@ func Contains(val cty.Value, mark valueMark) bool {
|
|||
return ret
|
||||
}
|
||||
|
||||
// FilterDeprecationMarks returns all deprecation marks present in the given
|
||||
// cty.ValueMarks.
|
||||
func FilterDeprecationMarks(marks cty.ValueMarks) []DeprecationMark {
|
||||
depMarks := []DeprecationMark{}
|
||||
for mark := range marks {
|
||||
if d, ok := mark.(DeprecationMark); ok {
|
||||
depMarks = append(depMarks, d)
|
||||
}
|
||||
}
|
||||
return depMarks
|
||||
}
|
||||
|
||||
// GetDeprecationMarks returns all deprecation marks present on the given
|
||||
// cty.Value.
|
||||
func GetDeprecationMarks(val cty.Value) []DeprecationMark {
|
||||
_, marks := val.UnmarkDeep()
|
||||
return FilterDeprecationMarks(marks)
|
||||
}
|
||||
|
||||
// RemoveDeprecationMarks returns a copy of the given cty.Value with all
|
||||
// deprecation marks removed.
|
||||
func RemoveDeprecationMarks(val cty.Value) cty.Value {
|
||||
newVal, pvms := val.UnmarkDeepWithPaths()
|
||||
otherPvms := RemoveAll(pvms, Deprecation)
|
||||
return newVal.MarkWithPaths(otherPvms)
|
||||
}
|
||||
|
||||
// Sensitive indicates that this value is marked as sensitive in the context of
|
||||
// Terraform.
|
||||
const Sensitive = valueMark("Sensitive")
|
||||
|
|
@ -51,3 +93,22 @@ const Ephemeral = valueMark("Ephemeral")
|
|||
// another value's type. This is part of the implementation of the console-only
|
||||
// `type` function.
|
||||
const TypeType = valueMark("TypeType")
|
||||
|
||||
type DeprecationMark struct {
|
||||
Message string
|
||||
Origin *hcl.Range
|
||||
}
|
||||
|
||||
func (d DeprecationMark) GoString() string {
|
||||
return "marks.deprecation<" + d.Message + ">"
|
||||
}
|
||||
|
||||
// Empty deprecation mark for usage in marks.Has / Contains / etc
|
||||
var Deprecation = NewDeprecation("", nil)
|
||||
|
||||
func NewDeprecation(message string, origin *hcl.Range) DeprecationMark {
|
||||
return DeprecationMark{
|
||||
Message: message,
|
||||
Origin: origin,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
internal/lang/marks/marks_test.go
Normal file
52
internal/lang/marks/marks_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package marks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestDeprecationMark(t *testing.T) {
|
||||
deprecationWithoutRange := cty.StringVal("OldValue").Mark(NewDeprecation("This is outdated", nil))
|
||||
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}}))
|
||||
|
||||
composite := cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": deprecationWithRange,
|
||||
"bar": deprecationWithoutRange,
|
||||
"baz": cty.StringVal("Not deprecated"),
|
||||
})
|
||||
|
||||
if !deprecationWithRange.IsMarked() {
|
||||
t.Errorf("Expected deprecationWithRange to be marked")
|
||||
}
|
||||
if !deprecationWithoutRange.IsMarked() {
|
||||
t.Errorf("Expected deprecationWithoutRange to be marked")
|
||||
}
|
||||
if composite.IsMarked() {
|
||||
t.Errorf("Expected composite to be marked")
|
||||
}
|
||||
|
||||
if !Has(deprecationWithRange, Deprecation) {
|
||||
t.Errorf("Expected deprecationWithRange to be marked with Deprecation")
|
||||
}
|
||||
if !Has(deprecationWithoutRange, Deprecation) {
|
||||
t.Errorf("Expected deprecationWithoutRange to be marked with Deprecation")
|
||||
}
|
||||
if Has(composite, Deprecation) {
|
||||
t.Errorf("Expected composite to be marked with Deprecation")
|
||||
}
|
||||
|
||||
if !Contains(deprecationWithRange, Deprecation) {
|
||||
t.Errorf("Expected deprecationWithRange to be contain Deprecation Mark")
|
||||
}
|
||||
if !Contains(deprecationWithoutRange, Deprecation) {
|
||||
t.Errorf("Expected deprecationWithoutRange to be contain Deprecation Mark")
|
||||
}
|
||||
if !Contains(composite, Deprecation) {
|
||||
t.Errorf("Expected composite to be contain Deprecation Mark")
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package marks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
|
@ -28,16 +29,36 @@ func PathsWithMark(pvms []cty.PathValueMarks, wantMark any) (withWanted []cty.Pa
|
|||
}
|
||||
|
||||
for _, pvm := range pvms {
|
||||
if _, ok := pvm.Marks[wantMark]; ok {
|
||||
pathHasMark := false
|
||||
pathHasOtherMarks := false
|
||||
for mark := range pvm.Marks {
|
||||
switch wantMark.(type) {
|
||||
case valueMark, string:
|
||||
if mark == wantMark {
|
||||
pathHasMark = true
|
||||
} else {
|
||||
pathHasOtherMarks = true
|
||||
}
|
||||
|
||||
// For data marks we check if a mark of the type exists
|
||||
case DeprecationMark:
|
||||
if _, ok := mark.(DeprecationMark); ok {
|
||||
pathHasMark = true
|
||||
} else {
|
||||
pathHasOtherMarks = true
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected mark type %T", wantMark))
|
||||
}
|
||||
}
|
||||
|
||||
if pathHasMark {
|
||||
withWanted = append(withWanted, pvm.Path)
|
||||
}
|
||||
|
||||
for mark := range pvm.Marks {
|
||||
if mark != wantMark {
|
||||
if pathHasOtherMarks {
|
||||
withOthers = append(withOthers, pvm)
|
||||
// only add a path with unwanted marks a single time
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +78,21 @@ func RemoveAll(pvms []cty.PathValueMarks, remove any) []cty.PathValueMarks {
|
|||
var res []cty.PathValueMarks
|
||||
|
||||
for _, pvm := range pvms {
|
||||
switch remove.(type) {
|
||||
case valueMark, string:
|
||||
delete(pvm.Marks, remove)
|
||||
|
||||
case DeprecationMark:
|
||||
// We want to delete all marks of this type
|
||||
for mark := range pvm.Marks {
|
||||
if _, ok := mark.(DeprecationMark); ok {
|
||||
delete(pvm.Marks, mark)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected mark type %T", remove))
|
||||
}
|
||||
if len(pvm.Marks) > 0 {
|
||||
res = append(res, pvm)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty-debug/ctydebug"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
|
@ -30,12 +31,25 @@ func TestPathsWithMark(t *testing.T) {
|
|||
Path: cty.GetAttrPath("neither"),
|
||||
Marks: cty.NewValueMarks("x", "y"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("deprecated"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecations"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil), "sensitive"),
|
||||
},
|
||||
}
|
||||
|
||||
gotPaths, gotOthers := PathsWithMark(input, "sensitive")
|
||||
wantPaths := []cty.Path{
|
||||
cty.GetAttrPath("sensitive"),
|
||||
cty.GetAttrPath("both"),
|
||||
cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
}
|
||||
wantOthers := []cty.PathValueMarks{
|
||||
{
|
||||
|
|
@ -56,6 +70,18 @@ func TestPathsWithMark(t *testing.T) {
|
|||
Path: cty.GetAttrPath("neither"),
|
||||
Marks: cty.NewValueMarks("x", "y"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("deprecated"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecations"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil), "sensitive"),
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantPaths, gotPaths, ctydebug.CmpOptions); diff != "" {
|
||||
|
|
@ -64,9 +90,46 @@ func TestPathsWithMark(t *testing.T) {
|
|||
if diff := cmp.Diff(wantOthers, gotOthers, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong set of entries with other marks\n%s", diff)
|
||||
}
|
||||
|
||||
gotPaths, gotOthers = PathsWithMark(input, Deprecation)
|
||||
|
||||
wantPaths = []cty.Path{
|
||||
cty.GetAttrPath("deprecated"),
|
||||
cty.GetAttrPath("multipleDeprecations"),
|
||||
cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
}
|
||||
wantOthers = []cty.PathValueMarks{
|
||||
{
|
||||
Path: cty.GetAttrPath("sensitive"),
|
||||
Marks: cty.NewValueMarks("sensitive"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("other"),
|
||||
Marks: cty.NewValueMarks("other"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("both"),
|
||||
Marks: cty.NewValueMarks("sensitive", "other"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("neither"),
|
||||
Marks: cty.NewValueMarks("x", "y"),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil), "sensitive"),
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantPaths, gotPaths, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong matched deprecation paths\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(wantOthers, gotOthers, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong set of entries with other than deprecation marks\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAll(t *testing.T) {
|
||||
func TestRemoveAll_valueMarks(t *testing.T) {
|
||||
input := []cty.PathValueMarks{
|
||||
{
|
||||
Path: cty.GetAttrPath("sensitive"),
|
||||
|
|
@ -100,6 +163,36 @@ func TestRemoveAll(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemoveAll_dataMarks(t *testing.T) {
|
||||
input := []cty.PathValueMarks{
|
||||
{
|
||||
Path: cty.GetAttrPath("deprecated"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecations"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil)),
|
||||
},
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
Marks: cty.NewValueMarks(NewDeprecation("this is deprecated", nil), NewDeprecation("this is also deprecated", nil), "sensitive"),
|
||||
},
|
||||
}
|
||||
|
||||
want := []cty.PathValueMarks{
|
||||
{
|
||||
Path: cty.GetAttrPath("multipleDeprecationsAndSensitive"),
|
||||
Marks: cty.NewValueMarks("sensitive"),
|
||||
},
|
||||
}
|
||||
|
||||
got := RemoveAll(input, Deprecation)
|
||||
|
||||
if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong matched paths\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkPaths(t *testing.T) {
|
||||
value := cty.ObjectVal(map[string]cty.Value{
|
||||
"s": cty.StringVal(".s"),
|
||||
|
|
@ -150,6 +243,38 @@ func TestMarkPaths(t *testing.T) {
|
|||
if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
|
||||
deprecatedPaths := []cty.Path{
|
||||
cty.GetAttrPath("s"),
|
||||
cty.GetAttrPath("l").IndexInt(1),
|
||||
cty.GetAttrPath("m").IndexString("a"),
|
||||
cty.GetAttrPath("o").GetAttr("b"),
|
||||
cty.GetAttrPath("t").IndexInt(0),
|
||||
}
|
||||
deprecationMark := NewDeprecation("this is deprecated", nil)
|
||||
got = MarkPaths(value, deprecationMark, deprecatedPaths)
|
||||
want = cty.ObjectVal(map[string]cty.Value{
|
||||
"s": cty.StringVal(".s").Mark(deprecationMark),
|
||||
"l": cty.ListVal([]cty.Value{
|
||||
cty.StringVal(".l[0]"),
|
||||
cty.StringVal(".l[1]").Mark(deprecationMark),
|
||||
}),
|
||||
"m": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.StringVal(`.m["a"]`).Mark(deprecationMark),
|
||||
"b": cty.StringVal(`.m["b"]`),
|
||||
}),
|
||||
"o": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal(".o.a"),
|
||||
"b": cty.StringVal(".o.b").Mark(deprecationMark),
|
||||
}),
|
||||
"t": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal(`.t[0]`).Mark(deprecationMark),
|
||||
cty.StringVal(`.t[1]`),
|
||||
}),
|
||||
})
|
||||
if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarksEqual(t *testing.T) {
|
||||
|
|
@ -239,6 +364,33 @@ func TestMarksEqual(t *testing.T) {
|
|||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("same message", nil))},
|
||||
},
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("same message", nil))},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("different", nil))},
|
||||
},
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("message", nil))},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("same message", &hcl.Range{Filename: "test.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 1}}))},
|
||||
},
|
||||
[]cty.PathValueMarks{
|
||||
{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks(NewDeprecation("same message", &hcl.Range{Filename: "otherFile.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 1}}))},
|
||||
},
|
||||
false, // TODO: Should this really be different?
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
if MarksEqual(tc.a, tc.b) != tc.equal {
|
||||
|
|
|
|||
|
|
@ -743,6 +743,7 @@ type Change struct {
|
|||
func (c *Change) Encode(schema *providers.Schema) (*ChangeSrc, error) {
|
||||
// We can't serialize value marks directly so we'll need to extract the
|
||||
// sensitive marks and store them in a separate field.
|
||||
// We ignore Deprecation marks.
|
||||
//
|
||||
// We don't accept any other marks here. The caller should have dealt
|
||||
// with those somehow and replaced them with unmarked placeholders before
|
||||
|
|
@ -751,6 +752,10 @@ func (c *Change) Encode(schema *providers.Schema) (*ChangeSrc, error) {
|
|||
unmarkedAfter, marksesAfter := c.After.UnmarkDeepWithPaths()
|
||||
sensitiveAttrsBefore, unsupportedMarksesBefore := marks.PathsWithMark(marksesBefore, marks.Sensitive)
|
||||
sensitiveAttrsAfter, unsupportedMarksesAfter := marks.PathsWithMark(marksesAfter, marks.Sensitive)
|
||||
|
||||
_, unsupportedMarksesBefore = marks.PathsWithMark(unsupportedMarksesBefore, marks.Deprecation)
|
||||
_, unsupportedMarksesAfter = marks.PathsWithMark(unsupportedMarksesAfter, marks.Deprecation)
|
||||
|
||||
if len(unsupportedMarksesBefore) != 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"prior value %s: can't serialize value marked with %#v (this is a bug in Terraform)",
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@ func (pc *PlannedChangeResourceInstancePlanned) ChangeDescription() (*stacks.Pla
|
|||
func DynamicValueToTerraform1(val cty.Value, ty cty.Type) (*stacks.DynamicValue, error) {
|
||||
unmarkedVal, markPaths := val.UnmarkDeepWithPaths()
|
||||
sensitivePaths, withOtherMarks := marks.PathsWithMark(markPaths, marks.Sensitive)
|
||||
_, withOtherMarks = marks.PathsWithMark(withOtherMarks, marks.Sensitive)
|
||||
if len(withOtherMarks) != 0 {
|
||||
return nil, withOtherMarks[0].Path.NewErrorf(
|
||||
"can't serialize value marked with %#v (this is a bug in Terraform)",
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ func (o *ResourceInstanceObject) AsTainted() *ResourceInstanceObject {
|
|||
func unmarkValueForStorage(v cty.Value) (unmarkedV cty.Value, sensitivePaths []cty.Path, err error) {
|
||||
val, pvms := v.UnmarkDeepWithPaths()
|
||||
sensitivePaths, withOtherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
|
||||
_, withOtherMarks = marks.PathsWithMark(withOtherMarks, marks.Deprecation)
|
||||
if len(withOtherMarks) != 0 {
|
||||
return cty.NilVal, nil, fmt.Errorf(
|
||||
"%s: cannot serialize value marked as %#v for inclusion in a state snapshot (this is a bug in Terraform)",
|
||||
|
|
|
|||
Loading…
Reference in a new issue