mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Ephemeral todos handling (#3177)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org> Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
committed by
Christian Mesh
parent
ccfeb83889
commit
1bab9aff46
@@ -241,10 +241,7 @@ func TestPrimaryChdirOption(t *testing.T) {
|
||||
}
|
||||
|
||||
// This test is checking the workflow of the ephemeral resources.
|
||||
// Check also the configuration files for comments. The idea is that at the time of
|
||||
// writing, the configuration was done in such a way to fail later when the
|
||||
// marks will be introduced for ephemeral values. Therefore, this test will
|
||||
// fail later and will require adjustments.
|
||||
// Check also the configuration files for comments.
|
||||
//
|
||||
// We want to validate that the plan file, state file and the output contain
|
||||
// only the things that are needed:
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
// should generally be suitable for display to an end-user anyway.
|
||||
//
|
||||
// This function will panic if the given value is not of an object type.
|
||||
// TODO ephemeral - check that ephemeral is not going to impact this
|
||||
func ObjectValueID(obj cty.Value) (k, v string) {
|
||||
if obj.IsNull() || !obj.IsKnown() {
|
||||
return "", ""
|
||||
@@ -37,7 +36,7 @@ func ObjectValueID(obj cty.Value) (k, v string) {
|
||||
|
||||
case atys["id"] == cty.String:
|
||||
v := obj.GetAttr("id")
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
if v.HasMark(marks.Sensitive) || v.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
v, _ = v.Unmark()
|
||||
@@ -50,7 +49,7 @@ func ObjectValueID(obj cty.Value) (k, v string) {
|
||||
// "name" isn't always globally unique, but if there isn't also an
|
||||
// "id" then it _often_ is, in practice.
|
||||
v := obj.GetAttr("name")
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
if v.HasMark(marks.Sensitive) || v.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
v, _ = v.Unmark()
|
||||
@@ -83,7 +82,6 @@ func ObjectValueID(obj cty.Value) (k, v string) {
|
||||
// name-extraction heuristics.
|
||||
//
|
||||
// This function will panic if the given value is not of an object type.
|
||||
// TODO ephemeral - check how (and if) ephemeral marks should have their own logic here. Check unit tests too.
|
||||
func ObjectValueName(obj cty.Value) (k, v string) {
|
||||
if obj.IsNull() || !obj.IsKnown() {
|
||||
return "", ""
|
||||
@@ -95,7 +93,7 @@ func ObjectValueName(obj cty.Value) (k, v string) {
|
||||
|
||||
case atys["name"] == cty.String:
|
||||
v := obj.GetAttr("name")
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
if v.HasMark(marks.Sensitive) || v.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
v, _ = v.Unmark()
|
||||
@@ -106,7 +104,7 @@ func ObjectValueName(obj cty.Value) (k, v string) {
|
||||
|
||||
case atys["tags"].IsMapType() && atys["tags"].ElementType() == cty.String:
|
||||
tags := obj.GetAttr("tags")
|
||||
if tags.IsNull() || !tags.IsWhollyKnown() || tags.HasMark(marks.Sensitive) {
|
||||
if tags.IsNull() || !tags.IsWhollyKnown() || tags.HasMark(marks.Sensitive) || tags.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
tags, _ = tags.Unmark()
|
||||
@@ -114,7 +112,7 @@ func ObjectValueName(obj cty.Value) (k, v string) {
|
||||
switch {
|
||||
case tags.HasIndex(cty.StringVal("name")).RawEquals(cty.True):
|
||||
v := tags.Index(cty.StringVal("name"))
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
if v.HasMark(marks.Sensitive) || v.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
v, _ = v.Unmark()
|
||||
@@ -125,7 +123,7 @@ func ObjectValueName(obj cty.Value) (k, v string) {
|
||||
case tags.HasIndex(cty.StringVal("Name")).RawEquals(cty.True):
|
||||
// AWS-style naming convention
|
||||
v := tags.Index(cty.StringVal("Name"))
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
if v.HasMark(marks.Sensitive) || v.HasMark(marks.Ephemeral) {
|
||||
break
|
||||
}
|
||||
v, _ = v.Unmark()
|
||||
|
||||
@@ -46,6 +46,22 @@ func TestObjectValueIDOrName(t *testing.T) {
|
||||
[...]string{"", ""},
|
||||
[...]string{"id", "foo-123"},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo-123").Mark(marks.Sensitive),
|
||||
}),
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo-123").Mark(marks.Ephemeral),
|
||||
}),
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo-123"),
|
||||
@@ -71,6 +87,14 @@ func TestObjectValueIDOrName(t *testing.T) {
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("awesome-bar").Mark(marks.Ephemeral),
|
||||
}),
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
[...]string{"", ""},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("awesome-foo"),
|
||||
|
||||
@@ -369,8 +369,9 @@ func newDiagnosticDifference(diag tfdiags.Diagnostic) *jsonplan.Change {
|
||||
// we cannot generate the same diff when the values involved are marked as ephemeral.
|
||||
// Therefore, for situations like this, we will return no diagnostic diff, making
|
||||
// the rendering of this to skip the diff part.
|
||||
// TODO ephemeral - later we can find a better solution for this, like changing the type
|
||||
// of the Diagnostic.Difference so that it can hold a generic type that can do this.
|
||||
// Later edit: As decided in opentofu/opentofu#3151, we decided
|
||||
// on not improving this diff rendering since it will show just a hint nonetheless.
|
||||
// If later will be needed, we can reconsider.
|
||||
if marks.Contains(lhs, marks.Ephemeral) || marks.Contains(rhs, marks.Ephemeral) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,9 +55,14 @@ func ComputeDiffForBlock(change structured.Change, block *jsonprovider.Block) co
|
||||
// values returned will be null.
|
||||
childChange := ComputeDiffForAttribute(childValue, attr, current)
|
||||
if childChange.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
|
||||
// This validation is specifically added for `tofu show`.
|
||||
// Since "current" will be NoOp during rendering the output for `tofu show`,
|
||||
// we need this validation to include the write-only attributes in the output.
|
||||
if !attr.WriteOnly {
|
||||
// Don't record nil values at all in blocks.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
attributes[key] = childChange
|
||||
current = collections.CompareActions(current, childChange.Action)
|
||||
|
||||
@@ -3451,6 +3451,34 @@ func TestWriteOnly_ComputeDiffForBlock(t *testing.T) {
|
||||
},
|
||||
}, plans.Create, false),
|
||||
},
|
||||
"write-only are shown during no-op too": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attr": {
|
||||
WriteOnly: true,
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
},
|
||||
"regular_attr": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_attr": nil,
|
||||
"regular_attr": "test",
|
||||
},
|
||||
Before: map[string]any{
|
||||
"write_only_attr": nil,
|
||||
"regular_attr": "test",
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attr": renderers.ValidateWriteOnly(plans.NoOp, false),
|
||||
"regular_attr": renderers.ValidatePrimitive("test", "test", plans.NoOp, false),
|
||||
}, nil, nil, nil, nil, plans.NoOp, false),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range cases {
|
||||
|
||||
@@ -79,7 +79,7 @@ func (plan Plan) renderHuman(renderer Renderer, mode plans.Mode, opts ...plans.Q
|
||||
continue
|
||||
}
|
||||
if diff.change.Mode == jsonstate.EphemeralResourceMode {
|
||||
// Do not render ephemeral changes. // TODO ephemeral add e2e test for this
|
||||
// Do not render ephemeral changes.
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO ephemeral - when implementing testing support for ephemeral resources, consider configuring ephemeral schema here
|
||||
// TODO ephemeral testing support - when implementing testing support for ephemeral resources, consider configuring ephemeral schema here
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ func (h *countHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generati
|
||||
defer h.Unlock()
|
||||
|
||||
// We don't count anything for data resources and neither for the ephemeral ones.
|
||||
// TODO ephemeral - test this after the ephemeral resources are introduced entirely
|
||||
if addr.Resource.Resource.Mode == addrs.DataResourceMode || addr.Resource.Resource.Mode == addrs.EphemeralResourceMode {
|
||||
return tofu.HookActionContinue, nil
|
||||
}
|
||||
|
||||
@@ -1096,12 +1096,12 @@ func (c *Config) transformOverriddenResourcesForTest(run *TestRun, file *TestFil
|
||||
}
|
||||
|
||||
if res.Mode != overrideRes.Mode {
|
||||
// TODO ephemeral - include also the ephemeral resource and the test_file.go#override_ephemeral
|
||||
// TODO ephemeral testing support - include also the ephemeral resource and the test_file.go#override_ephemeral
|
||||
blockName, targetMode := blockNameOverrideResource, "data"
|
||||
if overrideRes.Mode == addrs.DataResourceMode {
|
||||
blockName, targetMode = blockNameOverrideData, "resource"
|
||||
}
|
||||
//It could be a warning, but for the sake of consistent UX let's make it an error
|
||||
// It could be a warning, but for the sake of consistent UX let's make it an error
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unsupported `%v` target in `%v` block", targetMode, blockName),
|
||||
|
||||
@@ -263,7 +263,8 @@ type TestRunOptions struct {
|
||||
const (
|
||||
blockNameOverrideResource = "override_resource"
|
||||
blockNameOverrideData = "override_data"
|
||||
//blockNameOverrideEphemeral = "override_ephemeral" // TODO ephemeral uncomment this when testing support will be added for ephemerals
|
||||
// TODO ephemeral testing support - uncomment this when testing support will be added for ephemerals
|
||||
// blockNameOverrideEphemeral = "override_ephemeral"
|
||||
)
|
||||
|
||||
// OverrideResource contains information about a resource or data block to be overridden.
|
||||
|
||||
@@ -95,7 +95,7 @@ func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder,
|
||||
if !hclsyntax.ValidIdentifier(name) {
|
||||
name = string(hclwrite.TokensForValue(cty.StringVal(name)).Bytes())
|
||||
}
|
||||
fmt.Fprintf(buf, "%s = ", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = ", name)
|
||||
tok := hclwrite.TokensForValue(attrS.EmptyValue())
|
||||
if _, err := tok.WriteTo(buf); err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
@@ -113,7 +113,7 @@ func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder,
|
||||
if !hclsyntax.ValidIdentifier(name) {
|
||||
name = string(hclwrite.TokensForValue(cty.StringVal(name)).Bytes())
|
||||
}
|
||||
fmt.Fprintf(buf, "%s = ", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = ", name)
|
||||
tok := hclwrite.TokensForValue(attrS.EmptyValue())
|
||||
if _, err := tok.WriteTo(buf); err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
@@ -130,7 +130,6 @@ func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder,
|
||||
return diags
|
||||
}
|
||||
|
||||
// TODO ephemeral - check how ephemeral should be integrated in this function. Check also the unit tests
|
||||
func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, stateVal cty.Value, attrs map[string]*configschema.Attribute, indent int) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
if len(attrs) == 0 {
|
||||
@@ -155,7 +154,7 @@ func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *stri
|
||||
// Exclude computed-only attributes
|
||||
if attrS.Required || attrS.Optional {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = ", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = ", name)
|
||||
|
||||
var val cty.Value
|
||||
if !stateVal.IsNull() && stateVal.Type().HasAttribute(name) {
|
||||
@@ -164,10 +163,10 @@ func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *stri
|
||||
val = attrS.EmptyValue()
|
||||
}
|
||||
if attrS.Sensitive || val.HasMark(marks.Sensitive) {
|
||||
buf.WriteString("null # sensitive")
|
||||
_, _ = fmt.Fprintf(buf, "null # sensitive%s", writeOnlyComment(attrS, false))
|
||||
} else {
|
||||
if val.Type() == cty.String {
|
||||
unmarked, marks := val.Unmark()
|
||||
unmarked, valMarks := val.Unmark()
|
||||
|
||||
// SHAMELESS HACK: If we have "" for an optional value, assume
|
||||
// it is actually null, due to the legacy SDK.
|
||||
@@ -176,8 +175,8 @@ func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *stri
|
||||
}
|
||||
|
||||
// re-mark the value if it was marked originally
|
||||
if len(marks) > 0 {
|
||||
val = unmarked.Mark(marks)
|
||||
if len(valMarks) > 0 {
|
||||
val = unmarked.Mark(valMarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +191,7 @@ func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *stri
|
||||
})
|
||||
continue
|
||||
}
|
||||
_, _ = fmt.Fprintf(buf, "%s", writeOnlyComment(attrS, true))
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
@@ -228,7 +228,7 @@ func writeConfigNestedBlock(addr addrs.AbsResourceInstance, buf *strings.Builder
|
||||
switch schema.Nesting {
|
||||
case configschema.NestingSingle, configschema.NestingGroup:
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s {", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {", name)
|
||||
writeBlockTypeConstraint(buf, schema)
|
||||
diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
|
||||
diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
|
||||
@@ -236,7 +236,7 @@ func writeConfigNestedBlock(addr addrs.AbsResourceInstance, buf *strings.Builder
|
||||
return diags
|
||||
case configschema.NestingList, configschema.NestingSet:
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s {", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {", name)
|
||||
writeBlockTypeConstraint(buf, schema)
|
||||
diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
|
||||
diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
|
||||
@@ -245,7 +245,7 @@ func writeConfigNestedBlock(addr addrs.AbsResourceInstance, buf *strings.Builder
|
||||
case configschema.NestingMap:
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
// we use an arbitrary placeholder key (block label) "key"
|
||||
fmt.Fprintf(buf, "%s \"key\" {", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s \"key\" {", name)
|
||||
writeBlockTypeConstraint(buf, schema)
|
||||
diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
|
||||
diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
|
||||
@@ -262,7 +262,7 @@ func writeConfigNestedTypeAttribute(addr addrs.AbsResourceInstance, buf *strings
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = ", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = ", name)
|
||||
|
||||
switch schema.NestedType.Nesting {
|
||||
case configschema.NestingSingle:
|
||||
@@ -326,7 +326,6 @@ func writeConfigBlocksFromExisting(addr addrs.AbsResourceInstance, buf *strings.
|
||||
return diags
|
||||
}
|
||||
|
||||
// TODO ephemeral - check how ephemeral should be integrated in this function. Check also the unit tests
|
||||
func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.Attribute, stateVal cty.Value, indent int) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
@@ -334,7 +333,7 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
case configschema.NestingSingle:
|
||||
if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = {} # sensitive\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = {} # sensitive%s\n", name, writeOnlyComment(schema, false))
|
||||
return diags
|
||||
}
|
||||
|
||||
@@ -350,12 +349,12 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
// There is a difference between a null object, and an object with
|
||||
// no attributes.
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = null\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = null%s\n", name, writeOnlyComment(schema, true))
|
||||
return diags
|
||||
}
|
||||
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = {\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = {\n", name)
|
||||
diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, nestedVal, schema.NestedType.Attributes, indent+2))
|
||||
buf.WriteString("}\n")
|
||||
return diags
|
||||
@@ -364,7 +363,7 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
|
||||
if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = [] # sensitive\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = [] # sensitive%s\n", name, writeOnlyComment(schema, false))
|
||||
return diags
|
||||
}
|
||||
|
||||
@@ -372,15 +371,14 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
if listVals == nil {
|
||||
// There is a difference between an empty list and a null list
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = null\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = null%s\n", name, writeOnlyComment(schema, true))
|
||||
return diags
|
||||
}
|
||||
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = [\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = [\n", name)
|
||||
for i := range listVals {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
|
||||
// The entire element is marked.
|
||||
if listVals[i].HasMark(marks.Sensitive) {
|
||||
buf.WriteString("{}, # sensitive\n")
|
||||
@@ -399,7 +397,7 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
case configschema.NestingMap:
|
||||
if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = {} # sensitive\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = {} # sensitive%s\n", name, writeOnlyComment(schema, false))
|
||||
return diags
|
||||
}
|
||||
|
||||
@@ -407,7 +405,7 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
if attr.IsNull() {
|
||||
// There is a difference between an empty map and a null map.
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = null\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = null%s\n", name, writeOnlyComment(schema, true))
|
||||
return diags
|
||||
}
|
||||
|
||||
@@ -420,10 +418,10 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
sort.Strings(keys)
|
||||
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s = {\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s = {\n", name)
|
||||
for _, key := range keys {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
fmt.Fprintf(buf, "%s = {", key)
|
||||
_, _ = fmt.Fprintf(buf, "%s = {", key)
|
||||
|
||||
// This entire value is marked
|
||||
if vals[key].HasMark(marks.Sensitive) {
|
||||
@@ -446,7 +444,6 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ephemeral - check how ephemeral should be integrated in this function. Check also the unit tests
|
||||
func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.NestedBlock, stateVal cty.Value, indent int) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
@@ -456,7 +453,7 @@ func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *str
|
||||
return diags
|
||||
}
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s {", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {", name)
|
||||
|
||||
// If the entire value is marked, don't print any nested attributes
|
||||
if stateVal.HasMark(marks.Sensitive) {
|
||||
@@ -471,13 +468,13 @@ func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *str
|
||||
case configschema.NestingList, configschema.NestingSet:
|
||||
if stateVal.HasMark(marks.Sensitive) {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s {} # sensitive\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {} # sensitive\n", name)
|
||||
return diags
|
||||
}
|
||||
listVals := ctyCollectionValues(stateVal)
|
||||
for i := range listVals {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s {\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {\n", name)
|
||||
diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, listVals[i], schema.Attributes, indent+2))
|
||||
diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, listVals[i], schema.BlockTypes, indent+2))
|
||||
buf.WriteString("}\n")
|
||||
@@ -486,7 +483,7 @@ func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *str
|
||||
case configschema.NestingMap:
|
||||
// If the entire value is marked, don't print any nested attributes
|
||||
if stateVal.HasMark(marks.Sensitive) {
|
||||
fmt.Fprintf(buf, "%s {} # sensitive\n", name)
|
||||
_, _ = fmt.Fprintf(buf, "%s {} # sensitive\n", name)
|
||||
return diags
|
||||
}
|
||||
|
||||
@@ -499,7 +496,7 @@ func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *str
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(buf, "%s %q {", name, key)
|
||||
_, _ = fmt.Fprintf(buf, "%s %q {", name, key)
|
||||
// This entire map element is marked
|
||||
if vals[key].HasMark(marks.Sensitive) {
|
||||
buf.WriteString("} # sensitive\n")
|
||||
@@ -526,9 +523,9 @@ func writeAttrTypeConstraint(buf *strings.Builder, schema *configschema.Attribut
|
||||
}
|
||||
|
||||
if schema.NestedType != nil {
|
||||
fmt.Fprintf(buf, "%s\n", schema.NestedType.ImpliedType().FriendlyName())
|
||||
_, _ = fmt.Fprintf(buf, "%s\n", schema.NestedType.ImpliedType().FriendlyName())
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%s\n", schema.Type.FriendlyName())
|
||||
_, _ = fmt.Fprintf(buf, "%s\n", schema.Type.FriendlyName())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,15 +543,15 @@ func ctyCollectionValues(val cty.Value) []cty.Value {
|
||||
return nil
|
||||
}
|
||||
|
||||
var len int
|
||||
var length int
|
||||
if val.IsMarked() {
|
||||
val, _ = val.Unmark()
|
||||
len = val.LengthInt()
|
||||
length = val.LengthInt()
|
||||
} else {
|
||||
len = val.LengthInt()
|
||||
length = val.LengthInt()
|
||||
}
|
||||
|
||||
ret := make([]cty.Value, 0, len)
|
||||
ret := make([]cty.Value, 0, length)
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
_, value := it.Element()
|
||||
ret = append(ret, value)
|
||||
@@ -580,7 +577,7 @@ func omitUnknowns(val cty.Value) cty.Value {
|
||||
case ty.IsPrimitiveType():
|
||||
return val
|
||||
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
|
||||
unmarked, marks := val.Unmark()
|
||||
unmarked, valMarks := val.Unmark()
|
||||
var vals []cty.Value
|
||||
it := unmarked.ElementIterator()
|
||||
for it.Next() {
|
||||
@@ -598,9 +595,9 @@ func omitUnknowns(val cty.Value) cty.Value {
|
||||
// may have caused the individual elements to have different types,
|
||||
// and we're doing this work to produce JSON anyway and JSON marshalling
|
||||
// represents all of these sequence types as an array.
|
||||
return cty.TupleVal(vals).WithMarks(marks)
|
||||
return cty.TupleVal(vals).WithMarks(valMarks)
|
||||
case ty.IsMapType() || ty.IsObjectType():
|
||||
unmarked, marks := val.Unmark()
|
||||
unmarked, valMarks := val.Unmark()
|
||||
vals := make(map[string]cty.Value)
|
||||
it := unmarked.ElementIterator()
|
||||
for it.Next() {
|
||||
@@ -614,7 +611,7 @@ func omitUnknowns(val cty.Value) cty.Value {
|
||||
// may have caused the individual elements to have different types,
|
||||
// and we're doing this work to produce JSON anyway and JSON marshalling
|
||||
// represents both of these mapping types as an object.
|
||||
return cty.ObjectVal(vals).WithMarks(marks)
|
||||
return cty.ObjectVal(vals).WithMarks(valMarks)
|
||||
default:
|
||||
// Should never happen, since the above should cover all types
|
||||
panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val))
|
||||
@@ -661,3 +658,13 @@ func wrapAsJSONEncodeFunctionCall(v cty.Value) (hclwrite.Tokens, error) {
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func writeOnlyComment(attr *configschema.Attribute, startComment bool) string {
|
||||
if !attr.WriteOnly {
|
||||
return ""
|
||||
}
|
||||
if startComment {
|
||||
return " # write-only"
|
||||
}
|
||||
return " write-only"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ func TestConfigGeneration(t *testing.T) {
|
||||
Computed: computed,
|
||||
}
|
||||
}
|
||||
writeOnlyAttr := func(attribute *configschema.Attribute) *configschema.Attribute {
|
||||
attribute.WriteOnly = true
|
||||
return attribute
|
||||
}
|
||||
|
||||
tcs := map[string]struct {
|
||||
schema *configschema.Block
|
||||
@@ -724,6 +728,59 @@ resource "tfcoremock_simple_resource" "example" {
|
||||
sensitive_number = null # sensitive
|
||||
sensitive_object = null # sensitive
|
||||
sensitive_string = null # sensitive
|
||||
}`,
|
||||
},
|
||||
"simple_resource_with_write_only_and_sensitive": {
|
||||
// Write-only adds a new comment so we want to check that too
|
||||
|
||||
schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"sensitive_string": writeOnlyAttr(sensitiveVal(cty.String, false, true, false)),
|
||||
"sensitive_list": writeOnlyAttr(sensitiveVal(cty.List(cty.String), false, true, false)),
|
||||
"sensitive_map": writeOnlyAttr(sensitiveVal(cty.Map(cty.String), false, true, false)),
|
||||
"sensitive_object": writeOnlyAttr(sensitiveVal(cty.Object(map[string]cty.Type{}), false, true, false)),
|
||||
"single_nested_attribute": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSingle,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"single_nested_attribute_string": writeOnlyAttr(sensitiveVal(cty.String, false, true, false)),
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
addr: addrs.AbsResourceInstance{
|
||||
Module: nil,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "tfcoremock_simple_resource",
|
||||
Name: "example",
|
||||
},
|
||||
Key: nil,
|
||||
},
|
||||
},
|
||||
provider: addrs.LocalProviderConfig{
|
||||
LocalName: "tfcoremock",
|
||||
},
|
||||
value: cty.ObjectVal(map[string]cty.Value{
|
||||
"sensitive_string": cty.StringVal("sensitive").Mark(marks.Sensitive),
|
||||
"sensitive_list": cty.ListVal([]cty.Value{cty.StringVal("sensitive")}).Mark(marks.Sensitive),
|
||||
"sensitive_map": cty.MapVal(map[string]cty.Value{"key": cty.StringVal("sensitive")}).Mark(marks.Sensitive),
|
||||
"sensitive_object": cty.ObjectVal(map[string]cty.Value{}).Mark(marks.Sensitive),
|
||||
"single_nested_attribute": cty.ObjectVal(map[string]cty.Value{
|
||||
"single_nested_attribute_string": cty.StringVal("random"),
|
||||
})}),
|
||||
expected: `
|
||||
resource "tfcoremock_simple_resource" "example" {
|
||||
sensitive_list = null # sensitive write-only
|
||||
sensitive_map = null # sensitive write-only
|
||||
sensitive_object = null # sensitive write-only
|
||||
sensitive_string = null # sensitive write-only
|
||||
single_nested_attribute = {
|
||||
single_nested_attribute_string = null # sensitive write-only
|
||||
}
|
||||
}`,
|
||||
},
|
||||
"simple_resource_with_all_sensitive_computed_values": {
|
||||
|
||||
@@ -135,9 +135,6 @@ func performTypeAndValueChecks(expr hcl.Expression, hclCtx *hcl.EvalContext, all
|
||||
|
||||
}
|
||||
|
||||
// TODO ephemeral - check how ephemeral should impact this function. Check also the unit tests
|
||||
//
|
||||
// Christian: it should follow the same conventions as Sensitive and only be allowed in each.value and not in each.key.
|
||||
func performValueChecks(expr hcl.Expression, hclCtx *hcl.EvalContext, allowUnknown bool, forEachVal cty.Value, typeCheckVal cty.Value, errInvalidUnknownDetail string, excludableAddr addrs.Targetable) (cty.Value, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
ty := forEachVal.Type()
|
||||
@@ -186,6 +183,18 @@ func performValueChecks(expr hcl.Expression, hclCtx *hcl.EvalContext, allowUnkno
|
||||
})
|
||||
resultVal = cty.NullVal(ty)
|
||||
}
|
||||
if forEachVal.HasMark(marks.Ephemeral) {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments. If used, the ephemeral value could be exposed as a resource instance key.",
|
||||
Subject: expr.Range().Ptr(),
|
||||
Expression: expr,
|
||||
EvalContext: hclCtx,
|
||||
Extra: DiagnosticCausedByConfidentialValues(true),
|
||||
})
|
||||
resultVal = cty.NullVal(ty)
|
||||
}
|
||||
|
||||
return resultVal, diags
|
||||
}
|
||||
|
||||
@@ -56,6 +56,25 @@ func TestEvaluateForEachExpression_multi_errors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"ephemeral marked list": {
|
||||
hcltest.MockExprLiteral(cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")}).Mark(marks.Ephemeral)),
|
||||
[]struct {
|
||||
Summary string
|
||||
DetailSubstring string
|
||||
CausedBySensitive bool
|
||||
}{
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments. If used, the ephemeral value could be exposed as a resource instance key.",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"must be a map, or set of strings, and you have provided a value of type list",
|
||||
false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"marked tuple": {
|
||||
hcltest.MockExprLiteral(cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")}).Mark(marks.Sensitive)),
|
||||
[]struct {
|
||||
@@ -75,6 +94,25 @@ func TestEvaluateForEachExpression_multi_errors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"ephemeral marked tuple": {
|
||||
hcltest.MockExprLiteral(cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")}).Mark(marks.Ephemeral)),
|
||||
[]struct {
|
||||
Summary string
|
||||
DetailSubstring string
|
||||
CausedBySensitive bool
|
||||
}{
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments. If used, the ephemeral value could be exposed as a resource instance key.",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"must be a map, or set of strings, and you have provided a value of type tuple",
|
||||
false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"marked string": {
|
||||
hcltest.MockExprLiteral(cty.StringVal("a").Mark(marks.Sensitive)),
|
||||
[]struct {
|
||||
@@ -94,6 +132,25 @@ func TestEvaluateForEachExpression_multi_errors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"ephemeral marked string": {
|
||||
hcltest.MockExprLiteral(cty.StringVal("a").Mark(marks.Ephemeral)),
|
||||
[]struct {
|
||||
Summary string
|
||||
DetailSubstring string
|
||||
CausedBySensitive bool
|
||||
}{
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments. If used, the ephemeral value could be exposed as a resource instance key.",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid for_each argument",
|
||||
"must be a map, or set of strings, and you have provided a value of type string",
|
||||
false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
@@ -165,6 +222,17 @@ func TestEvaluateForEachExpressionValueTuple(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"ephemeral tuple": {
|
||||
Value: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}).Mark(marks.Ephemeral),
|
||||
expectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"unknown tuple": {
|
||||
Value: cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})),
|
||||
expectedErrs: []expectedErr{
|
||||
@@ -782,6 +850,77 @@ func TestEvaluateForEach(t *testing.T) {
|
||||
}},
|
||||
PlanReturnValue: map[string]cty.Value{},
|
||||
},
|
||||
"ephemeral_tuple": {
|
||||
Input: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}).Mark(marks.Ephemeral),
|
||||
ValidateExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
},
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "argument must be a map, or set of strings, and you have provided a value of type tuple.",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: false,
|
||||
},
|
||||
},
|
||||
ValidateReturnValue: cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
|
||||
PlanExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
},
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "argument must be a map, or set of strings, and you have provided a value of type tuple.",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: false,
|
||||
},
|
||||
},
|
||||
PlanReturnValue: map[string]cty.Value{},
|
||||
},
|
||||
"ephemeral_set": {
|
||||
Input: cty.SetVal([]cty.Value{cty.StringVal("a")}).Mark(marks.Ephemeral),
|
||||
ValidateExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
}},
|
||||
ValidateReturnValue: cty.NullVal(cty.Set(cty.String)),
|
||||
PlanExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
}},
|
||||
PlanReturnValue: map[string]cty.Value{},
|
||||
},
|
||||
"ephemeral_set_elements": {
|
||||
Input: cty.SetVal([]cty.Value{cty.StringVal("a").Mark(marks.Ephemeral)}),
|
||||
ValidateExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
}},
|
||||
ValidateReturnValue: cty.NullVal(cty.Set(cty.String)),
|
||||
PlanExpectedErrs: []expectedErr{
|
||||
{
|
||||
Summary: "Invalid for_each argument",
|
||||
Detail: "Ephemeral values, or values derived from ephemeral values, cannot be used as for_each arguments",
|
||||
CausedByUnknown: false,
|
||||
CausedBySensitive: true,
|
||||
}},
|
||||
PlanReturnValue: map[string]cty.Value{},
|
||||
},
|
||||
"string": {
|
||||
Input: cty.StringVal("i am definitely a set"),
|
||||
ValidateExpectedErrs: []expectedErr{
|
||||
|
||||
@@ -69,7 +69,6 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu
|
||||
return errs
|
||||
}
|
||||
|
||||
// TODO ephemeral - should this function be aware of the ephemeral mark? This needs proper testing and investigation
|
||||
func assertAttributeCompatible(plannedV, actualV cty.Value, attrS *configschema.Attribute, path cty.Path) []error {
|
||||
var errs []error
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func ProposedNew(schema *configschema.Block, prior, config cty.Value) cty.Value
|
||||
return proposedNew(schema, prior, config)
|
||||
}
|
||||
|
||||
// PlannedDataResourceObject is similar to proposedNewBlock but tailored for
|
||||
// PlannedUnknownObject is similar to proposedNewBlock but tailored for
|
||||
// planning data resources in particular. Specifically, it replaces the values
|
||||
// of any Computed attributes not set in the configuration with an unknown
|
||||
// value, which serves as a placeholder for a value to be filled in by the
|
||||
@@ -63,7 +63,16 @@ func ProposedNew(schema *configschema.Block, prior, config cty.Value) cty.Value
|
||||
// passing the proposedNewBlock result into a provider's PlanResourceChange
|
||||
// function, assuming a fixed implementation of PlanResourceChange that just
|
||||
// fills in unknown values as needed.
|
||||
func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty.Value {
|
||||
//
|
||||
// This is also used for generating the planned value of an ephemeral resource when
|
||||
// that is deferred. By design, ephemeral resources opening requires the configuration
|
||||
// of the block to be fully known. Therefore, when there is at least one unknown
|
||||
// attribute, we defer the execution until we resolve all the unknowns, and only
|
||||
// then we open the ephemeral. Until then, this particular function helps with
|
||||
// generating a placeholder for the ephemeral resource based on its schema.
|
||||
// Ephemeral resources are not stored into the state, so every newly planned value
|
||||
// is based only on the configuration and its schema.
|
||||
func PlannedUnknownObject(schema *configschema.Block, config cty.Value) cty.Value {
|
||||
// Our trick here is to run the proposedNewBlock logic with an
|
||||
// entirely-unknown prior value. Because of cty's unknown short-circuit
|
||||
// behavior, any operation on prior returns another unknown, and so
|
||||
@@ -73,16 +82,6 @@ func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty
|
||||
return proposedNew(schema, prior, config)
|
||||
}
|
||||
|
||||
// PlannedEphemeralResourceObject is exactly as PlannedDataResourceObject, but we
|
||||
// want to have a different copy of it to emphasize the special handling of
|
||||
// this type of resource.
|
||||
// Ephemeral resources are not stored into the state, so every newly planned value
|
||||
// is based only on the configuration and its schema.
|
||||
func PlannedEphemeralResourceObject(schema *configschema.Block, config cty.Value) cty.Value {
|
||||
prior := cty.UnknownVal(schema.ImpliedType())
|
||||
return proposedNew(schema, prior, config)
|
||||
}
|
||||
|
||||
func proposedNew(schema *configschema.Block, prior, config cty.Value) cty.Value {
|
||||
if config.IsNull() || !config.IsKnown() {
|
||||
// A block config should never be null at this point. The only nullable
|
||||
|
||||
@@ -102,7 +102,6 @@ type GRPCProvider struct {
|
||||
|
||||
var _ providers.Interface = new(GRPCProvider)
|
||||
|
||||
// TODO ephemeral - double check all of the usages of this to be sure that the block.ephemeral for ephemeral resources is used accordingly.
|
||||
func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
||||
logger.Trace("GRPCProvider.v6: GetProviderSchema")
|
||||
p.mu.Lock()
|
||||
|
||||
@@ -251,7 +251,7 @@ func (s simple) Close(_ context.Context) error {
|
||||
|
||||
func waitIfRequested(m map[string]cty.Value) {
|
||||
// This is a special case that can be used together with ephemeral resources to be able to test the renewal process.
|
||||
// When the "value" attribute of the resource is containing "with-renew" it will return later to allow
|
||||
// When the "value_wo" attribute of the resource is containing "with-renew" it will return later to allow
|
||||
// the ephemeral resource to call renew at least once. Check also OpenEphemeralResource.
|
||||
if v, ok := m["value_wo"]; ok && !v.IsNull() && strings.Contains(v.AsString(), "with-renew") {
|
||||
<-time.After(time.Second)
|
||||
|
||||
@@ -242,7 +242,7 @@ func (s simple) Close(_ context.Context) error {
|
||||
|
||||
func waitIfRequested(m map[string]cty.Value) {
|
||||
// This is a special case that can be used together with ephemeral resources to be able to test the renewal process.
|
||||
// When the "value" attribute of the resource is containing "with-renew" it will return later to allow
|
||||
// When the "value_wo" attribute of the resource is containing "with-renew" it will return later to allow
|
||||
// the ephemeral resource to call renew at least once. Check also OpenEphemeralResource.
|
||||
if v, ok := m["value_wo"]; ok && !v.IsNull() && strings.Contains(v.AsString(), "with-renew") {
|
||||
<-time.After(time.Second)
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// TODO ephemeral - check how ephemeral marks can be integrated in this function. Don't forget about unit tests
|
||||
|
||||
// FormatValue formats a value in a way that resembles OpenTofu language syntax
|
||||
// and uses the type conversion functions where necessary to indicate exactly
|
||||
// what type it is given, so that equality test failures can be quickly
|
||||
@@ -27,6 +25,9 @@ func FormatValue(v cty.Value, indent int) string {
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
return "(sensitive value)"
|
||||
}
|
||||
if v.HasMark(marks.Ephemeral) {
|
||||
return "(ephemeral value)"
|
||||
}
|
||||
if v.IsNull() {
|
||||
return formatNullValue(v.Type())
|
||||
}
|
||||
|
||||
@@ -179,6 +179,10 @@ EOT_`,
|
||||
cty.StringVal("a sensitive value").Mark(marks.Sensitive),
|
||||
"(sensitive value)",
|
||||
},
|
||||
{
|
||||
cty.StringVal("an ephemeral value").Mark(marks.Ephemeral),
|
||||
"(ephemeral value)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -1069,7 +1069,7 @@ func (d *evaluationStateData) GetOutput(_ context.Context, addr addrs.OutputValu
|
||||
if output.Sensitive {
|
||||
val = val.Mark(marks.Sensitive)
|
||||
}
|
||||
// TODO ephemeral - this GetOutput is used only during `tofu test` against root module outputs.
|
||||
// TODO ephemeral testing support - this GetOutput is used only during `tofu test` against root module outputs.
|
||||
// Therefore, since only the root module outputs can get in here, there is no reason to mark
|
||||
// values with ephemeral. Reanalyse this when implementing the testing support.
|
||||
// if config.Ephemeral {
|
||||
|
||||
@@ -196,7 +196,7 @@ func TestEvaluatorGetOutputValue(t *testing.T) {
|
||||
t.Errorf("wrong result %#v; want %#v", got, want)
|
||||
}
|
||||
|
||||
// TODO ephemeral - uncomment the line with the ephemeral mark once the testing support implementation is done
|
||||
// TODO ephemeral testing support - uncomment the line with the ephemeral mark once the testing support implementation is done
|
||||
// want = cty.StringVal("third").Mark(marks.Ephemeral)
|
||||
want = cty.StringVal("third")
|
||||
got, diags = scope.Data.GetOutput(t.Context(), addrs.OutputValue{
|
||||
|
||||
@@ -2080,7 +2080,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx context.Context, evalC
|
||||
}
|
||||
|
||||
unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths()
|
||||
proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
|
||||
proposedNewVal := objchange.PlannedUnknownObject(schema, unmarkedConfigVal)
|
||||
proposedNewVal = proposedNewVal.MarkWithPaths(configMarkPaths)
|
||||
|
||||
// Apply detects that the data source will need to be read by the After
|
||||
@@ -2141,7 +2141,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx context.Context, evalC
|
||||
// If we had errors, then we can cover that up by marking the new
|
||||
// state as unknown.
|
||||
unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths()
|
||||
newVal = objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
|
||||
newVal = objchange.PlannedUnknownObject(schema, unmarkedConfigVal)
|
||||
newVal = newVal.MarkWithPaths(configMarkPaths)
|
||||
|
||||
// We still want to report the check as failed even if we are still
|
||||
@@ -3346,7 +3346,7 @@ func (n *NodeAbstractResourceInstance) deferEphemeralResource(evalCtx EvalContex
|
||||
) {
|
||||
|
||||
unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths()
|
||||
proposedNewVal := objchange.PlannedEphemeralResourceObject(schema, unmarkedConfigVal)
|
||||
proposedNewVal := objchange.PlannedUnknownObject(schema, unmarkedConfigVal)
|
||||
proposedNewVal = proposedNewVal.MarkWithPaths(configMarkPaths)
|
||||
|
||||
plannedChange = &plans.ResourceInstanceChange{
|
||||
|
||||
@@ -114,17 +114,17 @@ func (p providerForTest) ReadDataSource(_ context.Context, r providers.ReadDataS
|
||||
}
|
||||
|
||||
func (p providerForTest) OpenEphemeralResource(_ context.Context, _ providers.OpenEphemeralResourceRequest) (resp providers.OpenEphemeralResourceResponse) {
|
||||
//TODO ephemeral - implement me when adding testing support
|
||||
// TODO ephemeral testing support - implement me when adding testing support
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (p providerForTest) RenewEphemeralResource(_ context.Context, _ providers.RenewEphemeralResourceRequest) (resp providers.RenewEphemeralResourceResponse) {
|
||||
//TODO ephemeral - implement me when adding testing support
|
||||
// TODO ephemeral testing support - implement me when adding testing support
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (p providerForTest) CloseEphemeralResource(_ context.Context, _ providers.CloseEphemeralResourceRequest) (resp providers.CloseEphemeralResourceResponse) {
|
||||
//TODO ephemeral - implement me when adding testing support
|
||||
// TODO ephemeral testing support - implement me when adding testing support
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user