-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ephemeral: validate provider responses for write-only attributes
- Loading branch information
1 parent
434a958
commit 1574f63
Showing
10 changed files
with
520 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package ephemeral | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform/internal/addrs" | ||
"github.com/hashicorp/terraform/internal/configs/configschema" | ||
"github.com/hashicorp/terraform/internal/tfdiags" | ||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
func ValidateWriteOnlyAttributes(newVal cty.Value, schema *configschema.Block, provider addrs.AbsProviderConfig, addr addrs.AbsResourceInstance) (diags tfdiags.Diagnostics) { | ||
if writeOnlyPaths := NonNullWriteOnlyPaths(newVal, schema, nil); len(writeOnlyPaths) != 0 { | ||
for _, p := range writeOnlyPaths { | ||
diags = diags.Append(tfdiags.Sourceless( | ||
tfdiags.Error, | ||
"Write-only attribute set", | ||
fmt.Sprintf( | ||
"Provider %q set the write-only attribute \"%s%s\". Write-only attributes must not be set by the provider, so this is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
provider.String(), addr.String(), tfdiags.FormatCtyPath(p), | ||
), | ||
)) | ||
} | ||
} | ||
return diags | ||
} | ||
|
||
// NonNullWriteOnlyPaths returns a list of paths to attributes that are write-only | ||
// and non-null in the given value. | ||
func NonNullWriteOnlyPaths(val cty.Value, schema *configschema.Block, p cty.Path) (paths []cty.Path) { | ||
if schema == nil { | ||
return paths | ||
} | ||
|
||
for name, attr := range schema.Attributes { | ||
attrPath := append(p, cty.GetAttrStep{Name: name}) | ||
attrVal, _ := attrPath.Apply(val) | ||
if attr.WriteOnly && !attrVal.IsNull() { | ||
paths = append(paths, attrPath) | ||
} | ||
} | ||
|
||
for name, blockS := range schema.BlockTypes { | ||
blockPath := append(p, cty.GetAttrStep{Name: name}) | ||
x := NonNullWriteOnlyPaths(val, &blockS.Block, blockPath) | ||
paths = append(paths, x...) | ||
} | ||
|
||
return paths | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package ephemeral | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/internal/configs/configschema" | ||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
func TestNonNullWriteOnlyPaths(t *testing.T) { | ||
for name, tc := range map[string]struct { | ||
val cty.Value | ||
schema *configschema.Block | ||
|
||
expectedPaths []cty.Path | ||
}{ | ||
"no write-only attributes": { | ||
val: cty.ObjectVal(map[string]cty.Value{ | ||
"id": cty.StringVal("i-abc123"), | ||
}), | ||
schema: &configschema.Block{ | ||
Attributes: map[string]*configschema.Attribute{ | ||
"id": { | ||
Type: cty.String, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
"write-only attribute with null value": { | ||
val: cty.ObjectVal(map[string]cty.Value{ | ||
"id": cty.NullVal(cty.String), | ||
}), | ||
schema: &configschema.Block{ | ||
Attributes: map[string]*configschema.Attribute{ | ||
"id": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
"write-only attribute with non-null value": { | ||
val: cty.ObjectVal(map[string]cty.Value{ | ||
"valid": cty.NullVal(cty.String), | ||
"id": cty.StringVal("i-abc123"), | ||
}), | ||
schema: &configschema.Block{ | ||
Attributes: map[string]*configschema.Attribute{ | ||
"valid": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
"id": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
}, | ||
}, | ||
expectedPaths: []cty.Path{cty.GetAttrPath("id")}, | ||
}, | ||
|
||
"write-only attributes in blocks": { | ||
val: cty.ObjectVal(map[string]cty.Value{ | ||
"foo": cty.ObjectVal(map[string]cty.Value{ | ||
"valid-write-only": cty.NullVal(cty.String), | ||
"valid": cty.StringVal("valid"), | ||
"id": cty.StringVal("i-abc123"), | ||
"bar": cty.ObjectVal(map[string]cty.Value{ | ||
"valid-write-only": cty.NullVal(cty.String), | ||
"valid": cty.StringVal("valid"), | ||
"id": cty.StringVal("i-abc123"), | ||
}), | ||
}), | ||
}), | ||
schema: &configschema.Block{ | ||
BlockTypes: map[string]*configschema.NestedBlock{ | ||
"foo": { | ||
Block: configschema.Block{ | ||
Attributes: map[string]*configschema.Attribute{ | ||
"valid-write-only": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
"valid": { | ||
Type: cty.String, | ||
Optional: true, | ||
}, | ||
"id": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
}, | ||
BlockTypes: map[string]*configschema.NestedBlock{ | ||
"bar": { | ||
Block: configschema.Block{ | ||
Attributes: map[string]*configschema.Attribute{ | ||
"valid-write-only": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
"valid": { | ||
Type: cty.String, | ||
Optional: true, | ||
}, | ||
"id": { | ||
Type: cty.String, | ||
Optional: true, | ||
WriteOnly: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
expectedPaths: []cty.Path{cty.GetAttrPath("foo").GetAttr("id"), cty.GetAttrPath("foo").GetAttr("bar").GetAttr("id")}, | ||
}, | ||
} { | ||
t.Run(name, func(t *testing.T) { | ||
paths := NonNullWriteOnlyPaths(tc.val, tc.schema, nil) | ||
|
||
if len(paths) != len(tc.expectedPaths) { | ||
t.Fatalf("expected %d write-only paths, got %d", len(tc.expectedPaths), len(paths)) | ||
} | ||
|
||
for i, path := range paths { | ||
if !path.Equals(tc.expectedPaths[i]) { | ||
t.Fatalf("expected path %#v, got %#v", tc.expectedPaths[i], path) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.