opentofu/internal/terraform/evaluate_valid_test.go
Martin Atkins 343279110a core: Graph walk loads plugin schemas opportunistically
Previously our graph walker expected to recieve a data structure
containing schemas for all of the provider and provisioner plugins used in
the configuration and state. That made sense back when
terraform.NewContext was responsible for loading all of the schemas before
taking any other action, but it no longer has that responsiblity.

Instead, we'll now make sure that the "contextPlugins" object reaches all
of the locations where we need schema -- many of which already had access
to that object anyway -- and then load the needed schemas just in time.

The contextPlugins object memoizes schema lookups, so we can safely call
it many times with the same provider address or provisioner type name and
know that it'll still only load each distinct plugin once per Context
object.

As of this commit, the Context.Schemas method is now a public interface
only and not used by logic in the "terraform" package at all. However,
that does leave us in a rather tenuous situation of relying on the fact
that all practical users of terraform.Context end up calling "Schemas" at
some point in order to verify that we have all of the expected versions
of plugins. That's a non-obvious implicit dependency, and so in subsequent
commits we'll gradually move all responsibility for verifying plugin
versions into the caller of terraform.NewContext, which'll heal a
long-standing architectural wart whereby the caller is responsible for
installing and locating the plugin executables but not for verifying that
what's installed is conforming to the current configuration and dependency
lock file.
2021-09-10 14:56:49 -07:00

122 lines
3.3 KiB
Go

package terraform
import (
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang"
)
func TestStaticValidateReferences(t *testing.T) {
tests := []struct {
Ref string
WantErr string
}{
{
"aws_instance.no_count",
``,
},
{
"aws_instance.count",
``,
},
{
"aws_instance.count[0]",
``,
},
{
"aws_instance.nonexist",
`Reference to undeclared resource: A managed resource "aws_instance" "nonexist" has not been declared in the root module.`,
},
{
"beep.boop",
`Reference to undeclared resource: A managed resource "beep" "boop" has not been declared in the root module.
Did you mean the data resource data.beep.boop?`,
},
{
"aws_instance.no_count[0]",
`Unexpected resource instance key: Because aws_instance.no_count does not have "count" or "for_each" set, references to it must not include an index key. Remove the bracketed index to refer to the single instance of this resource.`,
},
{
"aws_instance.count.foo",
// In this case we return two errors that are somewhat redundant with
// one another, but we'll accept that because they both report the
// problem from different perspectives and so give the user more
// opportunity to understand what's going on here.
`2 problems:
- Missing resource instance key: Because aws_instance.count has "count" set, its attributes must be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_instance.count[count.index]
- Unsupported attribute: This object has no argument, nested block, or exported attribute named "foo".`,
},
{
"boop_instance.yep",
``,
},
{
"boop_whatever.nope",
`Invalid resource type: A managed resource type "boop_whatever" is not supported by provider "registry.terraform.io/foobar/beep".`,
},
}
cfg := testModule(t, "static-validate-refs")
evaluator := &Evaluator{
Config: cfg,
Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]*ProviderSchema{
addrs.NewDefaultProvider("aws"): {
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {},
},
},
addrs.MustParseProviderSourceString("foobar/beep"): {
ResourceTypes: map[string]*configschema.Block{
// intentional mismatch between resource type prefix and provider type
"boop_instance": {},
},
},
}),
}
for _, test := range tests {
t.Run(test.Ref, func(t *testing.T) {
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.Ref), "", hcl.Pos{Line: 1, Column: 1})
if hclDiags.HasErrors() {
t.Fatal(hclDiags.Error())
}
refs, diags := lang.References([]hcl.Traversal{traversal})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
data := &evaluationStateData{
Evaluator: evaluator,
}
diags = data.StaticValidateReferences(refs, nil)
if diags.HasErrors() {
if test.WantErr == "" {
t.Fatalf("Unexpected diagnostics: %s", diags.Err())
}
gotErr := diags.Err().Error()
if gotErr != test.WantErr {
t.Fatalf("Wrong diagnostics\ngot: %s\nwant: %s", gotErr, test.WantErr)
}
return
}
if test.WantErr != "" {
t.Fatalf("Expected diagnostics, but got none\nwant: %s", test.WantErr)
}
})
}
}