github.com/opentofu/opentofu@v1.7.1/internal/tofu/graph_builder_plan_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs/configschema"
    17  	"github.com/opentofu/opentofu/internal/providers"
    18  )
    19  
    20  func TestPlanGraphBuilder_impl(t *testing.T) {
    21  	var _ GraphBuilder = new(PlanGraphBuilder)
    22  }
    23  
    24  func TestPlanGraphBuilder(t *testing.T) {
    25  	awsProvider := &MockProvider{
    26  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
    27  			Provider: providers.Schema{Block: simpleTestSchema()},
    28  			ResourceTypes: map[string]providers.Schema{
    29  				"aws_security_group": {Block: simpleTestSchema()},
    30  				"aws_instance":       {Block: simpleTestSchema()},
    31  				"aws_load_balancer":  {Block: simpleTestSchema()},
    32  			},
    33  		},
    34  	}
    35  	openstackProvider := mockProviderWithResourceTypeSchema("openstack_floating_ip", simpleTestSchema())
    36  	plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{
    37  		addrs.NewDefaultProvider("aws"):       providers.FactoryFixed(awsProvider),
    38  		addrs.NewDefaultProvider("openstack"): providers.FactoryFixed(openstackProvider),
    39  	}, t)
    40  
    41  	b := &PlanGraphBuilder{
    42  		Config:    testModule(t, "graph-builder-plan-basic"),
    43  		Plugins:   plugins,
    44  		Operation: walkPlan,
    45  	}
    46  
    47  	g, err := b.Build(addrs.RootModuleInstance)
    48  	if err != nil {
    49  		t.Fatalf("err: %s", err)
    50  	}
    51  
    52  	if g.Path.String() != addrs.RootModuleInstance.String() {
    53  		t.Fatalf("wrong module path %q", g.Path)
    54  	}
    55  
    56  	got := strings.TrimSpace(g.String())
    57  	want := strings.TrimSpace(testPlanGraphBuilderStr)
    58  	if diff := cmp.Diff(want, got); diff != "" {
    59  		t.Fatalf("wrong result\n%s", diff)
    60  	}
    61  }
    62  
    63  func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
    64  	provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{
    65  		Attributes: map[string]*configschema.Attribute{
    66  			"id":   {Type: cty.String, Computed: true},
    67  			"list": {Type: cty.List(cty.String), Computed: true},
    68  		},
    69  		BlockTypes: map[string]*configschema.NestedBlock{
    70  			"nested": {
    71  				Nesting: configschema.NestingList,
    72  				Block: configschema.Block{
    73  					Attributes: map[string]*configschema.Attribute{
    74  						"foo": {Type: cty.String, Optional: true},
    75  					},
    76  				},
    77  			},
    78  		},
    79  	})
    80  	plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{
    81  		addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
    82  	}, t)
    83  
    84  	b := &PlanGraphBuilder{
    85  		Config:    testModule(t, "graph-builder-plan-dynblock"),
    86  		Plugins:   plugins,
    87  		Operation: walkPlan,
    88  	}
    89  
    90  	g, err := b.Build(addrs.RootModuleInstance)
    91  	if err != nil {
    92  		t.Fatalf("err: %s", err)
    93  	}
    94  
    95  	if g.Path.String() != addrs.RootModuleInstance.String() {
    96  		t.Fatalf("wrong module path %q", g.Path)
    97  	}
    98  
    99  	// This test is here to make sure we properly detect references inside
   100  	// the special "dynamic" block construct. The most important thing here
   101  	// is that at the end test_thing.c depends on both test_thing.a and
   102  	// test_thing.b. Other details might shift over time as other logic in
   103  	// the graph builders changes.
   104  	got := strings.TrimSpace(g.String())
   105  	want := strings.TrimSpace(`
   106  provider["registry.opentofu.org/hashicorp/test"]
   107  provider["registry.opentofu.org/hashicorp/test"] (close)
   108    test_thing.c (expand)
   109  root
   110    provider["registry.opentofu.org/hashicorp/test"] (close)
   111  test_thing.a (expand)
   112    provider["registry.opentofu.org/hashicorp/test"]
   113  test_thing.b (expand)
   114    provider["registry.opentofu.org/hashicorp/test"]
   115  test_thing.c (expand)
   116    test_thing.a (expand)
   117    test_thing.b (expand)
   118  `)
   119  	if diff := cmp.Diff(want, got); diff != "" {
   120  		t.Fatalf("wrong result\n%s", diff)
   121  	}
   122  }
   123  
   124  func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
   125  	provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{
   126  		Attributes: map[string]*configschema.Attribute{
   127  			"id": {Type: cty.String, Computed: true},
   128  			"nested": {
   129  				Type: cty.List(cty.Object(map[string]cty.Type{
   130  					"foo": cty.String,
   131  				})),
   132  				Optional: true,
   133  			},
   134  		},
   135  	})
   136  	plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{
   137  		addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
   138  	}, t)
   139  
   140  	b := &PlanGraphBuilder{
   141  		Config:    testModule(t, "graph-builder-plan-attr-as-blocks"),
   142  		Plugins:   plugins,
   143  		Operation: walkPlan,
   144  	}
   145  
   146  	g, err := b.Build(addrs.RootModuleInstance)
   147  	if err != nil {
   148  		t.Fatalf("err: %s", err)
   149  	}
   150  
   151  	if g.Path.String() != addrs.RootModuleInstance.String() {
   152  		t.Fatalf("wrong module path %q", g.Path)
   153  	}
   154  
   155  	// This test is here to make sure we properly detect references inside
   156  	// the "nested" block that is actually defined in the schema as a
   157  	// list-of-objects attribute. This requires some special effort
   158  	// inside lang.ReferencesInBlock to make sure it searches blocks of
   159  	// type "nested" along with an attribute named "nested".
   160  	got := strings.TrimSpace(g.String())
   161  	want := strings.TrimSpace(`
   162  provider["registry.opentofu.org/hashicorp/test"]
   163  provider["registry.opentofu.org/hashicorp/test"] (close)
   164    test_thing.b (expand)
   165  root
   166    provider["registry.opentofu.org/hashicorp/test"] (close)
   167  test_thing.a (expand)
   168    provider["registry.opentofu.org/hashicorp/test"]
   169  test_thing.b (expand)
   170    test_thing.a (expand)
   171  `)
   172  	if diff := cmp.Diff(want, got); diff != "" {
   173  		t.Fatalf("wrong result\n%s", diff)
   174  	}
   175  }
   176  
   177  func TestPlanGraphBuilder_targetModule(t *testing.T) {
   178  	b := &PlanGraphBuilder{
   179  		Config:  testModule(t, "graph-builder-plan-target-module-provider"),
   180  		Plugins: simpleMockPluginLibrary(),
   181  		Targets: []addrs.Targetable{
   182  			addrs.RootModuleInstance.Child("child2", addrs.NoKey),
   183  		},
   184  		Operation: walkPlan,
   185  	}
   186  
   187  	g, err := b.Build(addrs.RootModuleInstance)
   188  	if err != nil {
   189  		t.Fatalf("err: %s", err)
   190  	}
   191  
   192  	t.Logf("Graph: %s", g.String())
   193  
   194  	testGraphNotContains(t, g, `module.child1.provider["registry.opentofu.org/hashicorp/test"]`)
   195  	testGraphNotContains(t, g, "module.child1.test_object.foo")
   196  }
   197  
   198  func TestPlanGraphBuilder_forEach(t *testing.T) {
   199  	awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
   200  
   201  	plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{
   202  		addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
   203  	}, t)
   204  
   205  	b := &PlanGraphBuilder{
   206  		Config:    testModule(t, "plan-for-each"),
   207  		Plugins:   plugins,
   208  		Operation: walkPlan,
   209  	}
   210  
   211  	g, err := b.Build(addrs.RootModuleInstance)
   212  	if err != nil {
   213  		t.Fatalf("err: %s", err)
   214  	}
   215  
   216  	if g.Path.String() != addrs.RootModuleInstance.String() {
   217  		t.Fatalf("wrong module path %q", g.Path)
   218  	}
   219  
   220  	got := strings.TrimSpace(g.String())
   221  	// We're especially looking for the edge here, where aws_instance.bat
   222  	// has a dependency on aws_instance.boo
   223  	want := strings.TrimSpace(testPlanGraphBuilderForEachStr)
   224  	if diff := cmp.Diff(want, got); diff != "" {
   225  		t.Fatalf("wrong result\n%s", diff)
   226  	}
   227  }
   228  
   229  const testPlanGraphBuilderStr = `
   230  aws_instance.web (expand)
   231    aws_security_group.firewall (expand)
   232    var.foo
   233  aws_load_balancer.weblb (expand)
   234    aws_instance.web (expand)
   235  aws_security_group.firewall (expand)
   236    provider["registry.opentofu.org/hashicorp/aws"]
   237  local.instance_id (expand)
   238    aws_instance.web (expand)
   239  openstack_floating_ip.random (expand)
   240    provider["registry.opentofu.org/hashicorp/openstack"]
   241  output.instance_id (expand)
   242    local.instance_id (expand)
   243  provider["registry.opentofu.org/hashicorp/aws"]
   244    openstack_floating_ip.random (expand)
   245  provider["registry.opentofu.org/hashicorp/aws"] (close)
   246    aws_load_balancer.weblb (expand)
   247  provider["registry.opentofu.org/hashicorp/openstack"]
   248  provider["registry.opentofu.org/hashicorp/openstack"] (close)
   249    openstack_floating_ip.random (expand)
   250  root
   251    output.instance_id (expand)
   252    provider["registry.opentofu.org/hashicorp/aws"] (close)
   253    provider["registry.opentofu.org/hashicorp/openstack"] (close)
   254  var.foo
   255  `
   256  const testPlanGraphBuilderForEachStr = `
   257  aws_instance.bar (expand)
   258    provider["registry.opentofu.org/hashicorp/aws"]
   259  aws_instance.bar2 (expand)
   260    provider["registry.opentofu.org/hashicorp/aws"]
   261  aws_instance.bat (expand)
   262    aws_instance.boo (expand)
   263  aws_instance.baz (expand)
   264    provider["registry.opentofu.org/hashicorp/aws"]
   265  aws_instance.boo (expand)
   266    provider["registry.opentofu.org/hashicorp/aws"]
   267  aws_instance.foo (expand)
   268    provider["registry.opentofu.org/hashicorp/aws"]
   269  provider["registry.opentofu.org/hashicorp/aws"]
   270  provider["registry.opentofu.org/hashicorp/aws"] (close)
   271    aws_instance.bar (expand)
   272    aws_instance.bar2 (expand)
   273    aws_instance.bat (expand)
   274    aws_instance.baz (expand)
   275    aws_instance.foo (expand)
   276  root
   277    provider["registry.opentofu.org/hashicorp/aws"] (close)
   278  `