mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Signed-off-by: Diogenes Fernandes <diofeher@gmail.com> Signed-off-by: Diógenes Fernandes <diofeher@gmail.com> Co-authored-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
631 lines
16 KiB
Go
631 lines
16 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/configs"
|
|
"github.com/opentofu/opentofu/internal/dag"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func testProviderTransformerGraph(t *testing.T, cfg *configs.Config) *Graph {
|
|
t.Helper()
|
|
|
|
g := &Graph{Path: addrs.RootModuleInstance}
|
|
ct := &ConfigTransformer{Config: cfg}
|
|
if err := ct.Transform(t.Context(), g); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
arct := &AttachResourceConfigTransformer{Config: cfg}
|
|
if err := arct.Transform(t.Context(), g); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return g
|
|
}
|
|
|
|
// This variant exists purely for testing and can not currently include the ProviderFunctionTransformer
|
|
func testTransformProviders(concrete ConcreteProviderNodeFunc, config *configs.Config) GraphTransformer {
|
|
return GraphTransformMulti(
|
|
// Add providers from the config
|
|
&ProviderConfigTransformer{
|
|
Config: config,
|
|
Concrete: concrete,
|
|
},
|
|
// Add any remaining missing providers
|
|
&MissingProviderTransformer{
|
|
Config: config,
|
|
Concrete: concrete,
|
|
},
|
|
// Connect the providers
|
|
&ProviderTransformer{
|
|
Config: config,
|
|
},
|
|
// Replace providers that have no config or dependencies to
|
|
// NodeEvalableProvider. This allows using provider-defined functions
|
|
// even when the provider isn't configured.
|
|
&ProviderUnconfiguredTransformer{},
|
|
|
|
// After schema transformer, we can add function references
|
|
&ProviderFunctionTransformer{Config: config, ProviderFunctionTracker: ProviderFunctionMapping{}},
|
|
// Remove unused providers and proxies
|
|
&PruneProviderTransformer{},
|
|
)
|
|
}
|
|
|
|
func TestProviderTransformer(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-basic")
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
transform := &MissingProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
transform := &ProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformProviderBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
// Test providers with FQNs that do not match the typeName
|
|
func TestProviderTransformer_fqns(t *testing.T) {
|
|
for _, mod := range []string{"fqns", "fqns-module"} {
|
|
mod := testModule(t, fmt.Sprintf("transform-provider-%s", mod))
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
transform := &MissingProviderTransformer{Config: mod}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
transform := &ProviderTransformer{Config: mod}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformProviderBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloseProviderTransformer(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-basic")
|
|
g := testProviderTransformerGraph(t, mod)
|
|
|
|
{
|
|
transform := &MissingProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &ProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &CloseProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformCloseProviderBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestCloseProviderTransformer_withTargets(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-basic")
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
transforms := []GraphTransformer{
|
|
&MissingProviderTransformer{},
|
|
&ProviderTransformer{},
|
|
&CloseProviderTransformer{},
|
|
&TargetingTransformer{
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "something", "else",
|
|
),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tr := range transforms {
|
|
if err := tr.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(``)
|
|
if actual != expected {
|
|
t.Fatalf("expected:%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestCloseProviderTransformer_withExcludes(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-basic")
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
transforms := []GraphTransformer{
|
|
&MissingProviderTransformer{},
|
|
&ProviderTransformer{},
|
|
&CloseProviderTransformer{},
|
|
&TargetingTransformer{
|
|
Excludes: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "web",
|
|
),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tr := range transforms {
|
|
if err := tr.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(``)
|
|
if actual != expected {
|
|
t.Fatalf("expected:%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestMissingProviderTransformer(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-missing")
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
transform := &MissingProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &ProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &CloseProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformMissingProviderBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestMissingProviderTransformer_grandchildMissing(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-missing-grandchild")
|
|
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
transform := testTransformProviders(concrete, mod)
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
{
|
|
transform := &TransitiveReductionTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformMissingGrandchildProviderStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestPruneProviderTransformer(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-prune")
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
transform := &MissingProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &ProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &CloseProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
{
|
|
transform := &PruneProviderTransformer{}
|
|
if err := transform.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformPruneProviderBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
// the child module resource is attached to the configured parent provider
|
|
func TestProviderConfigTransformer_parentProviders(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-inherit")
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
tf := testTransformProviders(concrete, mod)
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformModuleProviderConfigStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// the child module resource is attached to the configured grand-parent provider
|
|
func TestProviderConfigTransformer_grandparentProviders(t *testing.T) {
|
|
mod := testModule(t, "transform-provider-grandchild-inherit")
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
tf := testTransformProviders(concrete, mod)
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformModuleProviderGrandparentStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestProviderConfigTransformer_inheritOldSkool(t *testing.T) {
|
|
mod := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
provider "test" {
|
|
test_string = "config"
|
|
}
|
|
|
|
module "moda" {
|
|
source = "./moda"
|
|
}
|
|
`,
|
|
|
|
"moda/main.tf": `
|
|
resource "test_object" "a" {
|
|
}
|
|
`,
|
|
})
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
tf := testTransformProviders(concrete, mod)
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
expected := `module.moda.test_object.a
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
provider["registry.opentofu.org/hashicorp/test"]`
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Verify that configurations which are not recommended yet supported still work
|
|
func TestProviderConfigTransformer_nestedModuleProviders(t *testing.T) {
|
|
mod := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
terraform {
|
|
required_providers {
|
|
test = {
|
|
source = "registry.opentofu.org/hashicorp/test"
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "test" {
|
|
alias = "z"
|
|
test_string = "config"
|
|
}
|
|
|
|
module "moda" {
|
|
source = "./moda"
|
|
providers = {
|
|
test.x = test.z
|
|
}
|
|
}
|
|
`,
|
|
|
|
"moda/main.tf": `
|
|
terraform {
|
|
required_providers {
|
|
test = {
|
|
source = "registry.opentofu.org/hashicorp/test"
|
|
configuration_aliases = [ test.x ]
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "test" {
|
|
test_string = "config"
|
|
}
|
|
|
|
// this should connect to this module's provider
|
|
resource "test_object" "a" {
|
|
}
|
|
|
|
resource "test_object" "x" {
|
|
provider = test.x
|
|
}
|
|
|
|
module "modb" {
|
|
source = "./modb"
|
|
}
|
|
`,
|
|
|
|
"moda/modb/main.tf": `
|
|
# this should end up with the provider from the parent module
|
|
resource "test_object" "a" {
|
|
}
|
|
`,
|
|
})
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
{
|
|
tf := testTransformProviders(concrete, mod)
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
expected := `module.moda.module.modb.test_object.a
|
|
module.moda.provider["registry.opentofu.org/hashicorp/test"]
|
|
module.moda.provider["registry.opentofu.org/hashicorp/test"]
|
|
module.moda.test_object.a
|
|
module.moda.provider["registry.opentofu.org/hashicorp/test"]
|
|
module.moda.test_object.x
|
|
provider["registry.opentofu.org/hashicorp/test"].z
|
|
provider["registry.opentofu.org/hashicorp/test"].z`
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestProviderConfigTransformer_duplicateLocalName(t *testing.T) {
|
|
mod := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
terraform {
|
|
required_providers {
|
|
# We have to allow this since it wasn't previously prevented. If the
|
|
# default config is equivalent to the provider config, the user may never
|
|
# see an error.
|
|
dupe = {
|
|
source = "registry.opentofu.org/hashicorp/test"
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "test" {
|
|
}
|
|
`})
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex { return a }
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
tf := ProviderConfigTransformer{
|
|
Config: mod,
|
|
Concrete: concrete,
|
|
}
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := `provider["registry.opentofu.org/hashicorp/test"]`
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// TestProviderFunctionTransformer_onlyFunctions tests that the
|
|
// ProviderFunctionTransformer is removing NodeApplyableProvider
|
|
// and adding a NodeEvalableProvider in its place instead.
|
|
// This is useful so we can call functions without needing to
|
|
// configure the provider.
|
|
func TestProviderFunctionTransformer_onlyFunctions(t *testing.T) {
|
|
mod := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
terraform {
|
|
required_providers {
|
|
aws = {}
|
|
}
|
|
}
|
|
|
|
output "output_test" {
|
|
value = provider::aws::arn_build("aws", "s3", "", "", "test")
|
|
}
|
|
`})
|
|
concrete := func(a *NodeAbstractProvider) dag.Vertex {
|
|
return &NodeApplyableProvider{
|
|
a,
|
|
}
|
|
}
|
|
|
|
g := testProviderTransformerGraph(t, mod)
|
|
|
|
// Create a reference to the output
|
|
outputRef := &NodeApplyableOutput{
|
|
Addr: addrs.AbsOutputValue{
|
|
Module: addrs.RootModuleInstance,
|
|
OutputValue: addrs.OutputValue{
|
|
Name: "output_test",
|
|
},
|
|
},
|
|
Config: &configs.Output{
|
|
Name: "output_test",
|
|
Expr: &hclsyntax.FunctionCallExpr{
|
|
Name: "provider::aws::arn_build",
|
|
Args: []hclsyntax.Expression{
|
|
&hclsyntax.LiteralValueExpr{
|
|
Val: cty.StringVal("aws"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
g.Add(outputRef)
|
|
|
|
tf := testTransformProviders(concrete, mod)
|
|
if err := tf.Transform(t.Context(), g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := `output.output_test
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"]`
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
if diff := cmp.Diff(actual, expected); diff != "" {
|
|
t.Fatalf("expected: %s", diff)
|
|
}
|
|
edges := g.EdgesFrom(outputRef)
|
|
if len(edges) != 1 {
|
|
t.Fatalf("expecting 1 edge, got %d", len(edges))
|
|
}
|
|
edge := edges[0]
|
|
if _, ok := edge.Target().(*NodeEvalableProvider); !ok {
|
|
t.Fatalf("expecting NodeEvalableProvider provider, got %T", edge.Target())
|
|
}
|
|
}
|
|
|
|
const testTransformProviderBasicStr = `
|
|
aws_instance.web
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
`
|
|
|
|
const testTransformCloseProviderBasicStr = `
|
|
aws_instance.web
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"] (close)
|
|
aws_instance.web
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
`
|
|
|
|
const testTransformMissingProviderBasicStr = `
|
|
aws_instance.web
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
foo_instance.web
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"] (close)
|
|
aws_instance.web
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
provider["registry.opentofu.org/hashicorp/foo"] (close)
|
|
foo_instance.web
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
`
|
|
|
|
const testTransformMissingGrandchildProviderStr = `
|
|
module.sub.module.subsub.bar_instance.two
|
|
provider["registry.opentofu.org/hashicorp/bar"]
|
|
module.sub.module.subsub.foo_instance.one
|
|
module.sub.provider["registry.opentofu.org/hashicorp/foo"]
|
|
module.sub.provider["registry.opentofu.org/hashicorp/foo"]
|
|
provider["registry.opentofu.org/hashicorp/bar"]
|
|
`
|
|
|
|
const testTransformPruneProviderBasicStr = `
|
|
foo_instance.web
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
provider["registry.opentofu.org/hashicorp/foo"] (close)
|
|
foo_instance.web
|
|
provider["registry.opentofu.org/hashicorp/foo"]
|
|
`
|
|
|
|
const testTransformModuleProviderConfigStr = `
|
|
module.child.aws_instance.thing
|
|
provider["registry.opentofu.org/hashicorp/aws"].foo
|
|
provider["registry.opentofu.org/hashicorp/aws"].foo
|
|
`
|
|
|
|
const testTransformModuleProviderGrandparentStr = `
|
|
module.child.module.grandchild.aws_instance.baz
|
|
provider["registry.opentofu.org/hashicorp/aws"].foo
|
|
provider["registry.opentofu.org/hashicorp/aws"].foo
|
|
`
|