github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/plans/planfile/tfplan_test.go (about)

     1  package planfile
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/go-test/deep"
     8  	"github.com/zclconf/go-cty/cty"
     9  
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
    12  )
    13  
    14  func TestTFPlanRoundTrip(t *testing.T) {
    15  	objTy := cty.Object(map[string]cty.Type{
    16  		"id": cty.String,
    17  	})
    18  
    19  	plan := &plans.Plan{
    20  		VariableValues: map[string]plans.DynamicValue{
    21  			"foo": mustNewDynamicValueStr("foo value"),
    22  		},
    23  		Changes: &plans.Changes{
    24  			Outputs: []*plans.OutputChangeSrc{
    25  				{
    26  					Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
    27  					ChangeSrc: plans.ChangeSrc{
    28  						Action: plans.Create,
    29  						After:  mustDynamicOutputValue("bar value"),
    30  					},
    31  					Sensitive: false,
    32  				},
    33  				{
    34  					Addr: addrs.OutputValue{Name: "baz"}.Absolute(addrs.RootModuleInstance),
    35  					ChangeSrc: plans.ChangeSrc{
    36  						Action: plans.NoOp,
    37  						Before: mustDynamicOutputValue("baz value"),
    38  						After:  mustDynamicOutputValue("baz value"),
    39  					},
    40  					Sensitive: false,
    41  				},
    42  				{
    43  					Addr: addrs.OutputValue{Name: "secret"}.Absolute(addrs.RootModuleInstance),
    44  					ChangeSrc: plans.ChangeSrc{
    45  						Action: plans.Update,
    46  						Before: mustDynamicOutputValue("old secret value"),
    47  						After:  mustDynamicOutputValue("new secret value"),
    48  					},
    49  					Sensitive: true,
    50  				},
    51  			},
    52  			Resources: []*plans.ResourceInstanceChangeSrc{
    53  				{
    54  					Addr: addrs.Resource{
    55  						Mode: addrs.ManagedResourceMode,
    56  						Type: "test_thing",
    57  						Name: "woot",
    58  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
    59  					ProviderAddr: addrs.ProviderConfig{
    60  						Type: "test",
    61  					}.Absolute(addrs.RootModuleInstance),
    62  					ChangeSrc: plans.ChangeSrc{
    63  						Action: plans.DeleteThenCreate,
    64  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
    65  							"id": cty.StringVal("foo-bar-baz"),
    66  						}), objTy),
    67  						After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
    68  							"id": cty.UnknownVal(cty.String),
    69  						}), objTy),
    70  					},
    71  				},
    72  				{
    73  					Addr: addrs.Resource{
    74  						Mode: addrs.ManagedResourceMode,
    75  						Type: "test_thing",
    76  						Name: "woot",
    77  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
    78  					DeposedKey: "foodface",
    79  					ProviderAddr: addrs.ProviderConfig{
    80  						Type: "test",
    81  					}.Absolute(addrs.RootModuleInstance),
    82  					ChangeSrc: plans.ChangeSrc{
    83  						Action: plans.Delete,
    84  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
    85  							"id": cty.StringVal("bar-baz-foo"),
    86  						}), objTy),
    87  					},
    88  				},
    89  			},
    90  		},
    91  		TargetAddrs: []addrs.Targetable{
    92  			addrs.Resource{
    93  				Mode: addrs.ManagedResourceMode,
    94  				Type: "test_thing",
    95  				Name: "woot",
    96  			}.Absolute(addrs.RootModuleInstance),
    97  		},
    98  		ProviderSHA256s: map[string][]byte{
    99  			"test": {
   100  				0xba, 0x5e, 0x1e, 0x55, 0xb0, 0x1d, 0xfa, 0xce,
   101  				0xef, 0xfe, 0xc7, 0xed, 0x1a, 0xbe, 0x11, 0xed,
   102  				0x5c, 0xa1, 0xab, 0x1e, 0xda, 0x7a, 0xba, 0x5e,
   103  				0x70, 0x7a, 0x11, 0xed, 0xb0, 0x07, 0xab, 0x1e,
   104  			},
   105  		},
   106  		Backend: plans.Backend{
   107  			Type: "local",
   108  			Config: mustNewDynamicValue(
   109  				cty.ObjectVal(map[string]cty.Value{
   110  					"foo": cty.StringVal("bar"),
   111  				}),
   112  				cty.Object(map[string]cty.Type{
   113  					"foo": cty.String,
   114  				}),
   115  			),
   116  			Workspace: "default",
   117  		},
   118  	}
   119  
   120  	var buf bytes.Buffer
   121  	err := writeTfplan(plan, &buf)
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  
   126  	newPlan, err := readTfplan(&buf)
   127  	if err != nil {
   128  		t.Fatal(err)
   129  	}
   130  
   131  	{
   132  		oldDepth := deep.MaxDepth
   133  		oldCompare := deep.CompareUnexportedFields
   134  		deep.MaxDepth = 20
   135  		deep.CompareUnexportedFields = true
   136  		defer func() {
   137  			deep.MaxDepth = oldDepth
   138  			deep.CompareUnexportedFields = oldCompare
   139  		}()
   140  	}
   141  	for _, problem := range deep.Equal(newPlan, plan) {
   142  		t.Error(problem)
   143  	}
   144  }
   145  
   146  func mustDynamicOutputValue(val string) plans.DynamicValue {
   147  	ret, err := plans.NewDynamicValue(cty.StringVal(val), cty.DynamicPseudoType)
   148  	if err != nil {
   149  		panic(err)
   150  	}
   151  	return ret
   152  }
   153  
   154  func mustNewDynamicValue(val cty.Value, ty cty.Type) plans.DynamicValue {
   155  	ret, err := plans.NewDynamicValue(val, ty)
   156  	if err != nil {
   157  		panic(err)
   158  	}
   159  	return ret
   160  }
   161  
   162  func mustNewDynamicValueStr(val string) plans.DynamicValue {
   163  	realVal := cty.StringVal(val)
   164  	ret, err := plans.NewDynamicValue(realVal, cty.String)
   165  	if err != nil {
   166  		panic(err)
   167  	}
   168  	return ret
   169  }
   170  
   171  // TestTFPlanRoundTripDestroy ensures that encoding and decoding null values for
   172  // destroy doesn't leave us with any nil values.
   173  func TestTFPlanRoundTripDestroy(t *testing.T) {
   174  	objTy := cty.Object(map[string]cty.Type{
   175  		"id": cty.String,
   176  	})
   177  
   178  	plan := &plans.Plan{
   179  		Changes: &plans.Changes{
   180  			Outputs: []*plans.OutputChangeSrc{
   181  				{
   182  					Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
   183  					ChangeSrc: plans.ChangeSrc{
   184  						Action: plans.Delete,
   185  						Before: mustDynamicOutputValue("output"),
   186  						After:  mustNewDynamicValue(cty.NullVal(cty.String), cty.String),
   187  					},
   188  				},
   189  			},
   190  			Resources: []*plans.ResourceInstanceChangeSrc{
   191  				{
   192  					Addr: addrs.Resource{
   193  						Mode: addrs.ManagedResourceMode,
   194  						Type: "test_thing",
   195  						Name: "woot",
   196  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
   197  					ProviderAddr: addrs.ProviderConfig{
   198  						Type: "test",
   199  					}.Absolute(addrs.RootModuleInstance),
   200  					ChangeSrc: plans.ChangeSrc{
   201  						Action: plans.Delete,
   202  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
   203  							"id": cty.StringVal("foo-bar-baz"),
   204  						}), objTy),
   205  						After: mustNewDynamicValue(cty.NullVal(objTy), objTy),
   206  					},
   207  				},
   208  			},
   209  		},
   210  		TargetAddrs: []addrs.Targetable{
   211  			addrs.Resource{
   212  				Mode: addrs.ManagedResourceMode,
   213  				Type: "test_thing",
   214  				Name: "woot",
   215  			}.Absolute(addrs.RootModuleInstance),
   216  		},
   217  		Backend: plans.Backend{
   218  			Type: "local",
   219  			Config: mustNewDynamicValue(
   220  				cty.ObjectVal(map[string]cty.Value{
   221  					"foo": cty.StringVal("bar"),
   222  				}),
   223  				cty.Object(map[string]cty.Type{
   224  					"foo": cty.String,
   225  				}),
   226  			),
   227  			Workspace: "default",
   228  		},
   229  	}
   230  
   231  	var buf bytes.Buffer
   232  	err := writeTfplan(plan, &buf)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  
   237  	newPlan, err := readTfplan(&buf)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	for _, rics := range newPlan.Changes.Resources {
   243  		ric, err := rics.Decode(objTy)
   244  		if err != nil {
   245  			t.Fatal(err)
   246  		}
   247  
   248  		if ric.After == cty.NilVal {
   249  			t.Fatalf("unexpected nil After value: %#v\n", ric)
   250  		}
   251  	}
   252  	for _, ocs := range newPlan.Changes.Outputs {
   253  		oc, err := ocs.Decode()
   254  		if err != nil {
   255  			t.Fatal(err)
   256  		}
   257  
   258  		if oc.After == cty.NilVal {
   259  			t.Fatalf("unexpected nil After value: %#v\n", ocs)
   260  		}
   261  	}
   262  }