kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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  	"kubeform.dev/terraform-backend-sdk/addrs"
    11  	"kubeform.dev/terraform-backend-sdk/lang/marks"
    12  	"kubeform.dev/terraform-backend-sdk/plans"
    13  )
    14  
    15  func TestTFPlanRoundTrip(t *testing.T) {
    16  	objTy := cty.Object(map[string]cty.Type{
    17  		"id": cty.String,
    18  	})
    19  
    20  	plan := &plans.Plan{
    21  		VariableValues: map[string]plans.DynamicValue{
    22  			"foo": mustNewDynamicValueStr("foo value"),
    23  		},
    24  		Changes: &plans.Changes{
    25  			Outputs: []*plans.OutputChangeSrc{
    26  				{
    27  					Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
    28  					ChangeSrc: plans.ChangeSrc{
    29  						Action: plans.Create,
    30  						After:  mustDynamicOutputValue("bar value"),
    31  					},
    32  					Sensitive: false,
    33  				},
    34  				{
    35  					Addr: addrs.OutputValue{Name: "baz"}.Absolute(addrs.RootModuleInstance),
    36  					ChangeSrc: plans.ChangeSrc{
    37  						Action: plans.NoOp,
    38  						Before: mustDynamicOutputValue("baz value"),
    39  						After:  mustDynamicOutputValue("baz value"),
    40  					},
    41  					Sensitive: false,
    42  				},
    43  				{
    44  					Addr: addrs.OutputValue{Name: "secret"}.Absolute(addrs.RootModuleInstance),
    45  					ChangeSrc: plans.ChangeSrc{
    46  						Action: plans.Update,
    47  						Before: mustDynamicOutputValue("old secret value"),
    48  						After:  mustDynamicOutputValue("new secret value"),
    49  					},
    50  					Sensitive: true,
    51  				},
    52  			},
    53  			Resources: []*plans.ResourceInstanceChangeSrc{
    54  				{
    55  					Addr: addrs.Resource{
    56  						Mode: addrs.ManagedResourceMode,
    57  						Type: "test_thing",
    58  						Name: "woot",
    59  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
    60  					PrevRunAddr: addrs.Resource{
    61  						Mode: addrs.ManagedResourceMode,
    62  						Type: "test_thing",
    63  						Name: "woot",
    64  					}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    65  					ProviderAddr: addrs.AbsProviderConfig{
    66  						Provider: addrs.NewDefaultProvider("test"),
    67  						Module:   addrs.RootModule,
    68  					},
    69  					ChangeSrc: plans.ChangeSrc{
    70  						Action: plans.DeleteThenCreate,
    71  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
    72  							"id": cty.StringVal("foo-bar-baz"),
    73  							"boop": cty.ListVal([]cty.Value{
    74  								cty.StringVal("beep"),
    75  							}),
    76  						}), objTy),
    77  						After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
    78  							"id": cty.UnknownVal(cty.String),
    79  							"boop": cty.ListVal([]cty.Value{
    80  								cty.StringVal("beep"),
    81  								cty.StringVal("honk"),
    82  							}),
    83  						}), objTy),
    84  						AfterValMarks: []cty.PathValueMarks{
    85  							{
    86  								Path:  cty.GetAttrPath("boop").IndexInt(1),
    87  								Marks: cty.NewValueMarks(marks.Sensitive),
    88  							},
    89  						},
    90  					},
    91  					RequiredReplace: cty.NewPathSet(
    92  						cty.GetAttrPath("boop"),
    93  					),
    94  					ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
    95  				},
    96  				{
    97  					Addr: addrs.Resource{
    98  						Mode: addrs.ManagedResourceMode,
    99  						Type: "test_thing",
   100  						Name: "woot",
   101  					}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
   102  					PrevRunAddr: addrs.Resource{
   103  						Mode: addrs.ManagedResourceMode,
   104  						Type: "test_thing",
   105  						Name: "woot",
   106  					}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
   107  					DeposedKey: "foodface",
   108  					ProviderAddr: addrs.AbsProviderConfig{
   109  						Provider: addrs.NewDefaultProvider("test"),
   110  						Module:   addrs.RootModule,
   111  					},
   112  					ChangeSrc: plans.ChangeSrc{
   113  						Action: plans.Delete,
   114  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
   115  							"id": cty.StringVal("bar-baz-foo"),
   116  						}), objTy),
   117  					},
   118  				},
   119  			},
   120  		},
   121  		DriftedResources: []*plans.ResourceInstanceChangeSrc{
   122  			{
   123  				Addr: addrs.Resource{
   124  					Mode: addrs.ManagedResourceMode,
   125  					Type: "test_thing",
   126  					Name: "woot",
   127  				}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
   128  				PrevRunAddr: addrs.Resource{
   129  					Mode: addrs.ManagedResourceMode,
   130  					Type: "test_thing",
   131  					Name: "woot",
   132  				}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   133  				ProviderAddr: addrs.AbsProviderConfig{
   134  					Provider: addrs.NewDefaultProvider("test"),
   135  					Module:   addrs.RootModule,
   136  				},
   137  				ChangeSrc: plans.ChangeSrc{
   138  					Action: plans.DeleteThenCreate,
   139  					Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
   140  						"id": cty.StringVal("foo-bar-baz"),
   141  						"boop": cty.ListVal([]cty.Value{
   142  							cty.StringVal("beep"),
   143  						}),
   144  					}), objTy),
   145  					After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
   146  						"id": cty.UnknownVal(cty.String),
   147  						"boop": cty.ListVal([]cty.Value{
   148  							cty.StringVal("beep"),
   149  							cty.StringVal("bonk"),
   150  						}),
   151  					}), objTy),
   152  					AfterValMarks: []cty.PathValueMarks{
   153  						{
   154  							Path:  cty.GetAttrPath("boop").IndexInt(1),
   155  							Marks: cty.NewValueMarks(marks.Sensitive),
   156  						},
   157  					},
   158  				},
   159  			},
   160  		},
   161  		TargetAddrs: []addrs.Targetable{
   162  			addrs.Resource{
   163  				Mode: addrs.ManagedResourceMode,
   164  				Type: "test_thing",
   165  				Name: "woot",
   166  			}.Absolute(addrs.RootModuleInstance),
   167  		},
   168  		ProviderSHA256s: map[string][]byte{
   169  			"test": []byte{
   170  				0xba, 0x5e, 0x1e, 0x55, 0xb0, 0x1d, 0xfa, 0xce,
   171  				0xef, 0xfe, 0xc7, 0xed, 0x1a, 0xbe, 0x11, 0xed,
   172  				0x5c, 0xa1, 0xab, 0x1e, 0xda, 0x7a, 0xba, 0x5e,
   173  				0x70, 0x7a, 0x11, 0xed, 0xb0, 0x07, 0xab, 0x1e,
   174  			},
   175  		},
   176  		Backend: plans.Backend{
   177  			Type: "local",
   178  			Config: mustNewDynamicValue(
   179  				cty.ObjectVal(map[string]cty.Value{
   180  					"foo": cty.StringVal("bar"),
   181  				}),
   182  				cty.Object(map[string]cty.Type{
   183  					"foo": cty.String,
   184  				}),
   185  			),
   186  			Workspace: "default",
   187  		},
   188  	}
   189  
   190  	var buf bytes.Buffer
   191  	err := writeTfplan(plan, &buf)
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  
   196  	newPlan, err := readTfplan(&buf)
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  
   201  	{
   202  		oldDepth := deep.MaxDepth
   203  		oldCompare := deep.CompareUnexportedFields
   204  		deep.MaxDepth = 20
   205  		deep.CompareUnexportedFields = true
   206  		defer func() {
   207  			deep.MaxDepth = oldDepth
   208  			deep.CompareUnexportedFields = oldCompare
   209  		}()
   210  	}
   211  	for _, problem := range deep.Equal(newPlan, plan) {
   212  		t.Error(problem)
   213  	}
   214  }
   215  
   216  func mustDynamicOutputValue(val string) plans.DynamicValue {
   217  	ret, err := plans.NewDynamicValue(cty.StringVal(val), cty.DynamicPseudoType)
   218  	if err != nil {
   219  		panic(err)
   220  	}
   221  	return ret
   222  }
   223  
   224  func mustNewDynamicValue(val cty.Value, ty cty.Type) plans.DynamicValue {
   225  	ret, err := plans.NewDynamicValue(val, ty)
   226  	if err != nil {
   227  		panic(err)
   228  	}
   229  	return ret
   230  }
   231  
   232  func mustNewDynamicValueStr(val string) plans.DynamicValue {
   233  	realVal := cty.StringVal(val)
   234  	ret, err := plans.NewDynamicValue(realVal, cty.String)
   235  	if err != nil {
   236  		panic(err)
   237  	}
   238  	return ret
   239  }
   240  
   241  // TestTFPlanRoundTripDestroy ensures that encoding and decoding null values for
   242  // destroy doesn't leave us with any nil values.
   243  func TestTFPlanRoundTripDestroy(t *testing.T) {
   244  	objTy := cty.Object(map[string]cty.Type{
   245  		"id": cty.String,
   246  	})
   247  
   248  	plan := &plans.Plan{
   249  		Changes: &plans.Changes{
   250  			Outputs: []*plans.OutputChangeSrc{
   251  				{
   252  					Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
   253  					ChangeSrc: plans.ChangeSrc{
   254  						Action: plans.Delete,
   255  						Before: mustDynamicOutputValue("output"),
   256  						After:  mustNewDynamicValue(cty.NullVal(cty.String), cty.String),
   257  					},
   258  				},
   259  			},
   260  			Resources: []*plans.ResourceInstanceChangeSrc{
   261  				{
   262  					Addr: addrs.Resource{
   263  						Mode: addrs.ManagedResourceMode,
   264  						Type: "test_thing",
   265  						Name: "woot",
   266  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
   267  					PrevRunAddr: addrs.Resource{
   268  						Mode: addrs.ManagedResourceMode,
   269  						Type: "test_thing",
   270  						Name: "woot",
   271  					}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
   272  					ProviderAddr: addrs.AbsProviderConfig{
   273  						Provider: addrs.NewDefaultProvider("test"),
   274  						Module:   addrs.RootModule,
   275  					},
   276  					ChangeSrc: plans.ChangeSrc{
   277  						Action: plans.Delete,
   278  						Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
   279  							"id": cty.StringVal("foo-bar-baz"),
   280  						}), objTy),
   281  						After: mustNewDynamicValue(cty.NullVal(objTy), objTy),
   282  					},
   283  				},
   284  			},
   285  		},
   286  		DriftedResources: []*plans.ResourceInstanceChangeSrc{},
   287  		TargetAddrs: []addrs.Targetable{
   288  			addrs.Resource{
   289  				Mode: addrs.ManagedResourceMode,
   290  				Type: "test_thing",
   291  				Name: "woot",
   292  			}.Absolute(addrs.RootModuleInstance),
   293  		},
   294  		Backend: plans.Backend{
   295  			Type: "local",
   296  			Config: mustNewDynamicValue(
   297  				cty.ObjectVal(map[string]cty.Value{
   298  					"foo": cty.StringVal("bar"),
   299  				}),
   300  				cty.Object(map[string]cty.Type{
   301  					"foo": cty.String,
   302  				}),
   303  			),
   304  			Workspace: "default",
   305  		},
   306  	}
   307  
   308  	var buf bytes.Buffer
   309  	err := writeTfplan(plan, &buf)
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  
   314  	newPlan, err := readTfplan(&buf)
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	for _, rics := range newPlan.Changes.Resources {
   320  		ric, err := rics.Decode(objTy)
   321  		if err != nil {
   322  			t.Fatal(err)
   323  		}
   324  
   325  		if ric.After == cty.NilVal {
   326  			t.Fatalf("unexpected nil After value: %#v\n", ric)
   327  		}
   328  	}
   329  	for _, ocs := range newPlan.Changes.Outputs {
   330  		oc, err := ocs.Decode()
   331  		if err != nil {
   332  			t.Fatal(err)
   333  		}
   334  
   335  		if oc.After == cty.NilVal {
   336  			t.Fatalf("unexpected nil After value: %#v\n", ocs)
   337  		}
   338  	}
   339  }