|
|
|
|
@ -688,3 +688,349 @@ The attribute computed_string is decided by the provider alone and therefore the
|
|
|
|
|
t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Test_validateResourceForbiddenEphemeralValues(t *testing.T) {
|
|
|
|
|
simpleAttrs := map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
"input_wo": {Type: cty.String, Optional: true, WriteOnly: true},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dynAttrs := map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
"input_wo": {Type: cty.String, Optional: true, WriteOnly: true},
|
|
|
|
|
"dyn": {Type: cty.DynamicPseudoType, Optional: true},
|
|
|
|
|
"dyn_wo": {Type: cty.DynamicPseudoType, Optional: true, WriteOnly: true},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allAttrs := map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
"input_wo": {Type: cty.String, Optional: true, WriteOnly: true},
|
|
|
|
|
"dyn": {Type: cty.DynamicPseudoType, Optional: true},
|
|
|
|
|
"dyn_wo": {Type: cty.DynamicPseudoType, Optional: true, WriteOnly: true},
|
|
|
|
|
"nested_single_attr": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingSingle,
|
|
|
|
|
Attributes: dynAttrs,
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_list_attr": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingList,
|
|
|
|
|
Attributes: dynAttrs,
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_set_attr": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingSet,
|
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_single_attr_wo": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingSingle,
|
|
|
|
|
Attributes: simpleAttrs,
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
WriteOnly: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_list_attr_wo": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingList,
|
|
|
|
|
Attributes: dynAttrs,
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
WriteOnly: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_set_attr_wo": {
|
|
|
|
|
NestedType: &configschema.Object{
|
|
|
|
|
Nesting: configschema.NestingSet,
|
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Optional: true,
|
|
|
|
|
WriteOnly: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
schema := &configschema.Block{
|
|
|
|
|
Attributes: allAttrs,
|
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
|
"single": {
|
|
|
|
|
Block: configschema.Block{
|
|
|
|
|
Attributes: dynAttrs,
|
|
|
|
|
},
|
|
|
|
|
Nesting: configschema.NestingSingle,
|
|
|
|
|
},
|
|
|
|
|
"list": {
|
|
|
|
|
Block: configschema.Block{
|
|
|
|
|
Attributes: dynAttrs,
|
|
|
|
|
},
|
|
|
|
|
Nesting: configschema.NestingList,
|
|
|
|
|
},
|
|
|
|
|
"set": {
|
|
|
|
|
Block: configschema.Block{
|
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
|
"input": {Type: cty.String, Optional: true},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Nesting: configschema.NestingSet,
|
|
|
|
|
},
|
|
|
|
|
"map": {
|
|
|
|
|
Block: configschema.Block{
|
|
|
|
|
Attributes: simpleAttrs,
|
|
|
|
|
},
|
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := schema.InternalValidate(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type testCase struct {
|
|
|
|
|
obj cty.Value
|
|
|
|
|
valid bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := map[string]testCase{
|
|
|
|
|
"wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"dyn_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"dyn_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"nested_dyn_wo": {
|
|
|
|
|
// an ephemeral mark within a dynamic attribute is valid if the entire
|
|
|
|
|
// attr is write-only
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn_wo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_nested_dyn_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn_wo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_dyn_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"nested_single_attr_attr_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_single_attr": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_single_attr_attr_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_single_attr": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"nested_single_attr_wo_not_wo_attr": {
|
|
|
|
|
// we can assign an ephemeral to input because the outer
|
|
|
|
|
// nested_single_attr_wo attribute is write-only
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_single_attr_wo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_set_attr": {
|
|
|
|
|
// there is no possible input_wo because the schema validated that
|
|
|
|
|
// it cannot exist
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_set_attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"nested_set_attr_wo": {
|
|
|
|
|
// assigning an ephemeral to input is valid, because the outer set is write-only
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_set_attr_wo": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"nested_list_attr_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_list_attr": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"nested_list_attr_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"nested_list_attr_wo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"single_block_attr_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"single": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"single_block_attr_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"single": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"single_block_dyn_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"single": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn_wo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"single_block_dyn_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"single": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"list_block_attr_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"list_block_attr_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"list_block_dyn_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn_wo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"list_block_dyn_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"dyn": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"ephem": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"set_block_attr_wo": {
|
|
|
|
|
// the ephemeral value within a set will always transfer the mark to
|
|
|
|
|
// the outer set, but set blocks cannot be write-only
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"set": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
})}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
"map_block_attr_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"map": cty.MapVal(map[string]cty.Value{
|
|
|
|
|
"test": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input_wo": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: true,
|
|
|
|
|
},
|
|
|
|
|
"map_block_attr_not_wo": {
|
|
|
|
|
obj: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"map": cty.MapVal(map[string]cty.Value{
|
|
|
|
|
"test": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"input": cty.StringVal("wo").Mark(marks.Ephemeral),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
valid: false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, tc := range tests {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
val, err := schema.CoerceValue(tc.obj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
diags := validateResourceForbiddenEphemeralValues(nil, val, schema)
|
|
|
|
|
switch {
|
|
|
|
|
case tc.valid && diags.HasErrors():
|
|
|
|
|
t.Fatal("unexpected diags:", diags.ErrWithWarnings())
|
|
|
|
|
case !tc.valid && !diags.HasErrors():
|
|
|
|
|
t.Fatal("expected diagnostics, got none")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|