github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_diff_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/zclconf/go-cty/cty"
    13  
    14  	"github.com/opentofu/opentofu/internal/addrs"
    15  	"github.com/opentofu/opentofu/internal/plans"
    16  )
    17  
    18  func TestDiffTransformer_nilDiff(t *testing.T) {
    19  	g := Graph{Path: addrs.RootModuleInstance}
    20  	tf := &DiffTransformer{}
    21  	if err := tf.Transform(&g); err != nil {
    22  		t.Fatalf("err: %s", err)
    23  	}
    24  
    25  	if len(g.Vertices()) > 0 {
    26  		t.Fatal("graph should be empty")
    27  	}
    28  }
    29  
    30  func TestDiffTransformer(t *testing.T) {
    31  	g := Graph{Path: addrs.RootModuleInstance}
    32  
    33  	beforeVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	afterVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  
    42  	tf := &DiffTransformer{
    43  		Changes: &plans.Changes{
    44  			Resources: []*plans.ResourceInstanceChangeSrc{
    45  				{
    46  					Addr: addrs.Resource{
    47  						Mode: addrs.ManagedResourceMode,
    48  						Type: "aws_instance",
    49  						Name: "foo",
    50  					}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    51  					ProviderAddr: addrs.AbsProviderConfig{
    52  						Provider: addrs.NewDefaultProvider("aws"),
    53  						Module:   addrs.RootModule,
    54  					},
    55  					ChangeSrc: plans.ChangeSrc{
    56  						Action: plans.Update,
    57  						Before: beforeVal,
    58  						After:  afterVal,
    59  					},
    60  				},
    61  			},
    62  		},
    63  	}
    64  	if err := tf.Transform(&g); err != nil {
    65  		t.Fatalf("err: %s", err)
    66  	}
    67  
    68  	actual := strings.TrimSpace(g.String())
    69  	expected := strings.TrimSpace(testTransformDiffBasicStr)
    70  	if actual != expected {
    71  		t.Fatalf("bad:\n\n%s", actual)
    72  	}
    73  }
    74  
    75  func TestDiffTransformer_noOpChange(t *testing.T) {
    76  	// "No-op" changes are how we record explicitly in a plan that we did
    77  	// indeed visit a particular resource instance during the planning phase
    78  	// and concluded that no changes were needed, as opposed to the resource
    79  	// instance not existing at all or having been excluded from planning
    80  	// entirely.
    81  	//
    82  	// We must include nodes for resource instances with no-op changes in the
    83  	// apply graph, even though they won't take any external actions, because
    84  	// there are some secondary effects such as precondition/postcondition
    85  	// checks that can refer to objects elsewhere and so might have their
    86  	// results changed even if the resource instance they are attached to
    87  	// didn't actually change directly itself.
    88  
    89  	// aws_instance.foo has a precondition, so should be included in the final
    90  	// graph. aws_instance.bar has no conditions, so there is nothing to
    91  	// execute during apply and it should not be included in the graph.
    92  	m := testModuleInline(t, map[string]string{
    93  		"main.tf": `
    94  resource "aws_instance" "bar" {
    95  }
    96  
    97  resource "aws_instance" "foo" {
    98    test_string = "ok"
    99  
   100    lifecycle {
   101  	precondition {
   102  		condition     = self.test_string != ""
   103  		error_message = "resource error"
   104  	}
   105    }
   106  }
   107  `})
   108  
   109  	g := Graph{Path: addrs.RootModuleInstance}
   110  
   111  	beforeVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	tf := &DiffTransformer{
   117  		Config: m,
   118  		Changes: &plans.Changes{
   119  			Resources: []*plans.ResourceInstanceChangeSrc{
   120  				{
   121  					Addr: addrs.Resource{
   122  						Mode: addrs.ManagedResourceMode,
   123  						Type: "aws_instance",
   124  						Name: "foo",
   125  					}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   126  					ProviderAddr: addrs.AbsProviderConfig{
   127  						Provider: addrs.NewDefaultProvider("aws"),
   128  						Module:   addrs.RootModule,
   129  					},
   130  					ChangeSrc: plans.ChangeSrc{
   131  						// A "no-op" change has the no-op action and has the
   132  						// same object as both Before and After.
   133  						Action: plans.NoOp,
   134  						Before: beforeVal,
   135  						After:  beforeVal,
   136  					},
   137  				},
   138  				{
   139  					Addr: addrs.Resource{
   140  						Mode: addrs.ManagedResourceMode,
   141  						Type: "aws_instance",
   142  						Name: "bar",
   143  					}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   144  					ProviderAddr: addrs.AbsProviderConfig{
   145  						Provider: addrs.NewDefaultProvider("aws"),
   146  						Module:   addrs.RootModule,
   147  					},
   148  					ChangeSrc: plans.ChangeSrc{
   149  						// A "no-op" change has the no-op action and has the
   150  						// same object as both Before and After.
   151  						Action: plans.NoOp,
   152  						Before: beforeVal,
   153  						After:  beforeVal,
   154  					},
   155  				},
   156  			},
   157  		},
   158  	}
   159  	if err := tf.Transform(&g); err != nil {
   160  		t.Fatalf("err: %s", err)
   161  	}
   162  
   163  	actual := strings.TrimSpace(g.String())
   164  	expected := strings.TrimSpace(testTransformDiffBasicStr)
   165  	if actual != expected {
   166  		t.Fatalf("bad:\n\n%s", actual)
   167  	}
   168  }
   169  
   170  const testTransformDiffBasicStr = `
   171  aws_instance.foo
   172  `