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