github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonstate/state_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package jsonstate
     5  
     6  import (
     7  	"encoding/json"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/zclconf/go-cty/cty"
    13  
    14  	"github.com/terramate-io/tf/addrs"
    15  	"github.com/terramate-io/tf/configs/configschema"
    16  	"github.com/terramate-io/tf/lang/marks"
    17  	"github.com/terramate-io/tf/providers"
    18  	"github.com/terramate-io/tf/states"
    19  	"github.com/terramate-io/tf/terraform"
    20  )
    21  
    22  func TestMarshalOutputs(t *testing.T) {
    23  	tests := []struct {
    24  		Outputs map[string]*states.OutputValue
    25  		Want    map[string]Output
    26  		Err     bool
    27  	}{
    28  		{
    29  			nil,
    30  			nil,
    31  			false,
    32  		},
    33  		{
    34  			map[string]*states.OutputValue{
    35  				"test": {
    36  					Sensitive: true,
    37  					Value:     cty.StringVal("sekret"),
    38  				},
    39  			},
    40  			map[string]Output{
    41  				"test": {
    42  					Sensitive: true,
    43  					Value:     json.RawMessage(`"sekret"`),
    44  					Type:      json.RawMessage(`"string"`),
    45  				},
    46  			},
    47  			false,
    48  		},
    49  		{
    50  			map[string]*states.OutputValue{
    51  				"test": {
    52  					Sensitive: false,
    53  					Value:     cty.StringVal("not_so_sekret"),
    54  				},
    55  			},
    56  			map[string]Output{
    57  				"test": {
    58  					Sensitive: false,
    59  					Value:     json.RawMessage(`"not_so_sekret"`),
    60  					Type:      json.RawMessage(`"string"`),
    61  				},
    62  			},
    63  			false,
    64  		},
    65  		{
    66  			map[string]*states.OutputValue{
    67  				"mapstring": {
    68  					Sensitive: false,
    69  					Value: cty.MapVal(map[string]cty.Value{
    70  						"beep": cty.StringVal("boop"),
    71  					}),
    72  				},
    73  				"setnumber": {
    74  					Sensitive: false,
    75  					Value: cty.SetVal([]cty.Value{
    76  						cty.NumberIntVal(3),
    77  						cty.NumberIntVal(5),
    78  						cty.NumberIntVal(7),
    79  						cty.NumberIntVal(11),
    80  					}),
    81  				},
    82  			},
    83  			map[string]Output{
    84  				"mapstring": {
    85  					Sensitive: false,
    86  					Value:     json.RawMessage(`{"beep":"boop"}`),
    87  					Type:      json.RawMessage(`["map","string"]`),
    88  				},
    89  				"setnumber": {
    90  					Sensitive: false,
    91  					Value:     json.RawMessage(`[3,5,7,11]`),
    92  					Type:      json.RawMessage(`["set","number"]`),
    93  				},
    94  			},
    95  			false,
    96  		},
    97  	}
    98  
    99  	for _, test := range tests {
   100  		got, err := MarshalOutputs(test.Outputs)
   101  		if test.Err {
   102  			if err == nil {
   103  				t.Fatal("succeeded; want error")
   104  			}
   105  			return
   106  		} else if err != nil {
   107  			t.Fatalf("unexpected error: %s", err)
   108  		}
   109  		if !cmp.Equal(test.Want, got) {
   110  			t.Fatalf("wrong result:\n%s", cmp.Diff(test.Want, got))
   111  		}
   112  	}
   113  }
   114  
   115  func TestMarshalAttributeValues(t *testing.T) {
   116  	tests := []struct {
   117  		Attr cty.Value
   118  		Want AttributeValues
   119  	}{
   120  		{
   121  			cty.NilVal,
   122  			nil,
   123  		},
   124  		{
   125  			cty.NullVal(cty.String),
   126  			nil,
   127  		},
   128  		{
   129  			cty.ObjectVal(map[string]cty.Value{
   130  				"foo": cty.StringVal("bar"),
   131  			}),
   132  			AttributeValues{"foo": json.RawMessage(`"bar"`)},
   133  		},
   134  		{
   135  			cty.ObjectVal(map[string]cty.Value{
   136  				"foo": cty.NullVal(cty.String),
   137  			}),
   138  			AttributeValues{"foo": json.RawMessage(`null`)},
   139  		},
   140  		{
   141  			cty.ObjectVal(map[string]cty.Value{
   142  				"bar": cty.MapVal(map[string]cty.Value{
   143  					"hello": cty.StringVal("world"),
   144  				}),
   145  				"baz": cty.ListVal([]cty.Value{
   146  					cty.StringVal("goodnight"),
   147  					cty.StringVal("moon"),
   148  				}),
   149  			}),
   150  			AttributeValues{
   151  				"bar": json.RawMessage(`{"hello":"world"}`),
   152  				"baz": json.RawMessage(`["goodnight","moon"]`),
   153  			},
   154  		},
   155  		// Marked values
   156  		{
   157  			cty.ObjectVal(map[string]cty.Value{
   158  				"bar": cty.MapVal(map[string]cty.Value{
   159  					"hello": cty.StringVal("world"),
   160  				}),
   161  				"baz": cty.ListVal([]cty.Value{
   162  					cty.StringVal("goodnight"),
   163  					cty.StringVal("moon").Mark(marks.Sensitive),
   164  				}),
   165  			}),
   166  			AttributeValues{
   167  				"bar": json.RawMessage(`{"hello":"world"}`),
   168  				"baz": json.RawMessage(`["goodnight","moon"]`),
   169  			},
   170  		},
   171  	}
   172  
   173  	for _, test := range tests {
   174  		got := marshalAttributeValues(test.Attr)
   175  		eq := reflect.DeepEqual(got, test.Want)
   176  		if !eq {
   177  			t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
   178  		}
   179  	}
   180  }
   181  
   182  func TestMarshalResources(t *testing.T) {
   183  	deposedKey := states.NewDeposedKey()
   184  	tests := map[string]struct {
   185  		Resources map[string]*states.Resource
   186  		Schemas   *terraform.Schemas
   187  		Want      []Resource
   188  		Err       bool
   189  	}{
   190  		"nil": {
   191  			nil,
   192  			nil,
   193  			nil,
   194  			false,
   195  		},
   196  		"single resource": {
   197  			map[string]*states.Resource{
   198  				"test_thing.baz": {
   199  					Addr: addrs.AbsResource{
   200  						Resource: addrs.Resource{
   201  							Mode: addrs.ManagedResourceMode,
   202  							Type: "test_thing",
   203  							Name: "bar",
   204  						},
   205  					},
   206  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   207  						addrs.NoKey: {
   208  							Current: &states.ResourceInstanceObjectSrc{
   209  								Status:    states.ObjectReady,
   210  								AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   211  							},
   212  						},
   213  					},
   214  					ProviderConfig: addrs.AbsProviderConfig{
   215  						Provider: addrs.NewDefaultProvider("test"),
   216  						Module:   addrs.RootModule,
   217  					},
   218  				},
   219  			},
   220  			testSchemas(),
   221  			[]Resource{
   222  				{
   223  					Address:      "test_thing.bar",
   224  					Mode:         "managed",
   225  					Type:         "test_thing",
   226  					Name:         "bar",
   227  					Index:        nil,
   228  					ProviderName: "registry.terraform.io/hashicorp/test",
   229  					AttributeValues: AttributeValues{
   230  						"foozles": json.RawMessage(`null`),
   231  						"woozles": json.RawMessage(`"confuzles"`),
   232  					},
   233  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   234  				},
   235  			},
   236  			false,
   237  		},
   238  		"single resource_with_sensitive": {
   239  			map[string]*states.Resource{
   240  				"test_thing.baz": {
   241  					Addr: addrs.AbsResource{
   242  						Resource: addrs.Resource{
   243  							Mode: addrs.ManagedResourceMode,
   244  							Type: "test_thing",
   245  							Name: "bar",
   246  						},
   247  					},
   248  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   249  						addrs.NoKey: {
   250  							Current: &states.ResourceInstanceObjectSrc{
   251  								Status:    states.ObjectReady,
   252  								AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles"}`),
   253  							},
   254  						},
   255  					},
   256  					ProviderConfig: addrs.AbsProviderConfig{
   257  						Provider: addrs.NewDefaultProvider("test"),
   258  						Module:   addrs.RootModule,
   259  					},
   260  				},
   261  			},
   262  			testSchemas(),
   263  			[]Resource{
   264  				{
   265  					Address:      "test_thing.bar",
   266  					Mode:         "managed",
   267  					Type:         "test_thing",
   268  					Name:         "bar",
   269  					Index:        nil,
   270  					ProviderName: "registry.terraform.io/hashicorp/test",
   271  					AttributeValues: AttributeValues{
   272  						"foozles": json.RawMessage(`"sensuzles"`),
   273  						"woozles": json.RawMessage(`"confuzles"`),
   274  					},
   275  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   276  				},
   277  			},
   278  			false,
   279  		},
   280  		"resource with marks": {
   281  			map[string]*states.Resource{
   282  				"test_thing.bar": {
   283  					Addr: addrs.AbsResource{
   284  						Resource: addrs.Resource{
   285  							Mode: addrs.ManagedResourceMode,
   286  							Type: "test_thing",
   287  							Name: "bar",
   288  						},
   289  					},
   290  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   291  						addrs.NoKey: {
   292  							Current: &states.ResourceInstanceObjectSrc{
   293  								Status:    states.ObjectReady,
   294  								AttrsJSON: []byte(`{"foozles":"confuzles"}`),
   295  								AttrSensitivePaths: []cty.PathValueMarks{{
   296  									Path:  cty.Path{cty.GetAttrStep{Name: "foozles"}},
   297  									Marks: cty.NewValueMarks(marks.Sensitive)},
   298  								},
   299  							},
   300  						},
   301  					},
   302  					ProviderConfig: addrs.AbsProviderConfig{
   303  						Provider: addrs.NewDefaultProvider("test"),
   304  						Module:   addrs.RootModule,
   305  					},
   306  				},
   307  			},
   308  			testSchemas(),
   309  			[]Resource{
   310  				{
   311  					Address:      "test_thing.bar",
   312  					Mode:         "managed",
   313  					Type:         "test_thing",
   314  					Name:         "bar",
   315  					Index:        nil,
   316  					ProviderName: "registry.terraform.io/hashicorp/test",
   317  					AttributeValues: AttributeValues{
   318  						"foozles": json.RawMessage(`"confuzles"`),
   319  						"woozles": json.RawMessage(`null`),
   320  					},
   321  					SensitiveValues: json.RawMessage(`{"foozles":true}`),
   322  				},
   323  			},
   324  			false,
   325  		},
   326  		"single resource wrong schema": {
   327  			map[string]*states.Resource{
   328  				"test_thing.baz": {
   329  					Addr: addrs.AbsResource{
   330  						Resource: addrs.Resource{
   331  							Mode: addrs.ManagedResourceMode,
   332  							Type: "test_thing",
   333  							Name: "bar",
   334  						},
   335  					},
   336  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   337  						addrs.NoKey: {
   338  							Current: &states.ResourceInstanceObjectSrc{
   339  								SchemaVersion: 1,
   340  								Status:        states.ObjectReady,
   341  								AttrsJSON:     []byte(`{"woozles":["confuzles"]}`),
   342  							},
   343  						},
   344  					},
   345  					ProviderConfig: addrs.AbsProviderConfig{
   346  						Provider: addrs.NewDefaultProvider("test"),
   347  						Module:   addrs.RootModule,
   348  					},
   349  				},
   350  			},
   351  			testSchemas(),
   352  			nil,
   353  			true,
   354  		},
   355  		"resource with count": {
   356  			map[string]*states.Resource{
   357  				"test_thing.bar": {
   358  					Addr: addrs.AbsResource{
   359  						Resource: addrs.Resource{
   360  							Mode: addrs.ManagedResourceMode,
   361  							Type: "test_thing",
   362  							Name: "bar",
   363  						},
   364  					},
   365  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   366  						addrs.IntKey(0): {
   367  							Current: &states.ResourceInstanceObjectSrc{
   368  								Status:    states.ObjectReady,
   369  								AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   370  							},
   371  						},
   372  					},
   373  					ProviderConfig: addrs.AbsProviderConfig{
   374  						Provider: addrs.NewDefaultProvider("test"),
   375  						Module:   addrs.RootModule,
   376  					},
   377  				},
   378  			},
   379  			testSchemas(),
   380  			[]Resource{
   381  				{
   382  					Address:      "test_thing.bar[0]",
   383  					Mode:         "managed",
   384  					Type:         "test_thing",
   385  					Name:         "bar",
   386  					Index:        json.RawMessage(`0`),
   387  					ProviderName: "registry.terraform.io/hashicorp/test",
   388  					AttributeValues: AttributeValues{
   389  						"foozles": json.RawMessage(`null`),
   390  						"woozles": json.RawMessage(`"confuzles"`),
   391  					},
   392  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   393  				},
   394  			},
   395  			false,
   396  		},
   397  		"resource with for_each": {
   398  			map[string]*states.Resource{
   399  				"test_thing.bar": {
   400  					Addr: addrs.AbsResource{
   401  						Resource: addrs.Resource{
   402  							Mode: addrs.ManagedResourceMode,
   403  							Type: "test_thing",
   404  							Name: "bar",
   405  						},
   406  					},
   407  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   408  						addrs.StringKey("rockhopper"): {
   409  							Current: &states.ResourceInstanceObjectSrc{
   410  								Status:    states.ObjectReady,
   411  								AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   412  							},
   413  						},
   414  					},
   415  					ProviderConfig: addrs.AbsProviderConfig{
   416  						Provider: addrs.NewDefaultProvider("test"),
   417  						Module:   addrs.RootModule,
   418  					},
   419  				},
   420  			},
   421  			testSchemas(),
   422  			[]Resource{
   423  				{
   424  					Address:      "test_thing.bar[\"rockhopper\"]",
   425  					Mode:         "managed",
   426  					Type:         "test_thing",
   427  					Name:         "bar",
   428  					Index:        json.RawMessage(`"rockhopper"`),
   429  					ProviderName: "registry.terraform.io/hashicorp/test",
   430  					AttributeValues: AttributeValues{
   431  						"foozles": json.RawMessage(`null`),
   432  						"woozles": json.RawMessage(`"confuzles"`),
   433  					},
   434  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   435  				},
   436  			},
   437  			false,
   438  		},
   439  		"deposed resource": {
   440  			map[string]*states.Resource{
   441  				"test_thing.baz": {
   442  					Addr: addrs.AbsResource{
   443  						Resource: addrs.Resource{
   444  							Mode: addrs.ManagedResourceMode,
   445  							Type: "test_thing",
   446  							Name: "bar",
   447  						},
   448  					},
   449  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   450  						addrs.NoKey: {
   451  							Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
   452  								states.DeposedKey(deposedKey): {
   453  									Status:    states.ObjectReady,
   454  									AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   455  								},
   456  							},
   457  						},
   458  					},
   459  					ProviderConfig: addrs.AbsProviderConfig{
   460  						Provider: addrs.NewDefaultProvider("test"),
   461  						Module:   addrs.RootModule,
   462  					},
   463  				},
   464  			},
   465  			testSchemas(),
   466  			[]Resource{
   467  				{
   468  					Address:      "test_thing.bar",
   469  					Mode:         "managed",
   470  					Type:         "test_thing",
   471  					Name:         "bar",
   472  					Index:        nil,
   473  					ProviderName: "registry.terraform.io/hashicorp/test",
   474  					DeposedKey:   deposedKey.String(),
   475  					AttributeValues: AttributeValues{
   476  						"foozles": json.RawMessage(`null`),
   477  						"woozles": json.RawMessage(`"confuzles"`),
   478  					},
   479  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   480  				},
   481  			},
   482  			false,
   483  		},
   484  		"deposed and current resource": {
   485  			map[string]*states.Resource{
   486  				"test_thing.baz": {
   487  					Addr: addrs.AbsResource{
   488  						Resource: addrs.Resource{
   489  							Mode: addrs.ManagedResourceMode,
   490  							Type: "test_thing",
   491  							Name: "bar",
   492  						},
   493  					},
   494  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   495  						addrs.NoKey: {
   496  							Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
   497  								states.DeposedKey(deposedKey): {
   498  									Status:    states.ObjectReady,
   499  									AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   500  								},
   501  							},
   502  							Current: &states.ResourceInstanceObjectSrc{
   503  								Status:    states.ObjectReady,
   504  								AttrsJSON: []byte(`{"woozles":"confuzles"}`),
   505  							},
   506  						},
   507  					},
   508  					ProviderConfig: addrs.AbsProviderConfig{
   509  						Provider: addrs.NewDefaultProvider("test"),
   510  						Module:   addrs.RootModule,
   511  					},
   512  				},
   513  			},
   514  			testSchemas(),
   515  			[]Resource{
   516  				{
   517  					Address:      "test_thing.bar",
   518  					Mode:         "managed",
   519  					Type:         "test_thing",
   520  					Name:         "bar",
   521  					Index:        nil,
   522  					ProviderName: "registry.terraform.io/hashicorp/test",
   523  					AttributeValues: AttributeValues{
   524  						"foozles": json.RawMessage(`null`),
   525  						"woozles": json.RawMessage(`"confuzles"`),
   526  					},
   527  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   528  				},
   529  				{
   530  					Address:      "test_thing.bar",
   531  					Mode:         "managed",
   532  					Type:         "test_thing",
   533  					Name:         "bar",
   534  					Index:        nil,
   535  					ProviderName: "registry.terraform.io/hashicorp/test",
   536  					DeposedKey:   deposedKey.String(),
   537  					AttributeValues: AttributeValues{
   538  						"foozles": json.RawMessage(`null`),
   539  						"woozles": json.RawMessage(`"confuzles"`),
   540  					},
   541  					SensitiveValues: json.RawMessage("{\"foozles\":true}"),
   542  				},
   543  			},
   544  			false,
   545  		},
   546  		"resource with marked map attr": {
   547  			map[string]*states.Resource{
   548  				"test_map_attr.bar": {
   549  					Addr: addrs.AbsResource{
   550  						Resource: addrs.Resource{
   551  							Mode: addrs.ManagedResourceMode,
   552  							Type: "test_map_attr",
   553  							Name: "bar",
   554  						},
   555  					},
   556  					Instances: map[addrs.InstanceKey]*states.ResourceInstance{
   557  						addrs.NoKey: {
   558  							Current: &states.ResourceInstanceObjectSrc{
   559  								Status:    states.ObjectReady,
   560  								AttrsJSON: []byte(`{"data":{"woozles":"confuzles"}}`),
   561  								AttrSensitivePaths: []cty.PathValueMarks{{
   562  									Path:  cty.Path{cty.GetAttrStep{Name: "data"}},
   563  									Marks: cty.NewValueMarks(marks.Sensitive)},
   564  								},
   565  							},
   566  						},
   567  					},
   568  					ProviderConfig: addrs.AbsProviderConfig{
   569  						Provider: addrs.NewDefaultProvider("test"),
   570  						Module:   addrs.RootModule,
   571  					},
   572  				},
   573  			},
   574  			testSchemas(),
   575  			[]Resource{
   576  				{
   577  					Address:      "test_map_attr.bar",
   578  					Mode:         "managed",
   579  					Type:         "test_map_attr",
   580  					Name:         "bar",
   581  					Index:        nil,
   582  					ProviderName: "registry.terraform.io/hashicorp/test",
   583  					AttributeValues: AttributeValues{
   584  						"data": json.RawMessage(`{"woozles":"confuzles"}`),
   585  					},
   586  					SensitiveValues: json.RawMessage(`{"data":true}`),
   587  				},
   588  			},
   589  			false,
   590  		},
   591  	}
   592  
   593  	for name, test := range tests {
   594  		t.Run(name, func(t *testing.T) {
   595  			got, err := marshalResources(test.Resources, addrs.RootModuleInstance, test.Schemas)
   596  			if test.Err {
   597  				if err == nil {
   598  					t.Fatal("succeeded; want error")
   599  				}
   600  				return
   601  			} else if err != nil {
   602  				t.Fatalf("unexpected error: %s", err)
   603  			}
   604  
   605  			diff := cmp.Diff(got, test.Want)
   606  			if diff != "" {
   607  				t.Fatalf("wrong result: %s\n", diff)
   608  			}
   609  
   610  		})
   611  	}
   612  }
   613  
   614  func TestMarshalModules_basic(t *testing.T) {
   615  	childModule, _ := addrs.ParseModuleInstanceStr("module.child")
   616  	subModule, _ := addrs.ParseModuleInstanceStr("module.submodule")
   617  	testState := states.BuildState(func(s *states.SyncState) {
   618  		s.SetResourceInstanceCurrent(
   619  			addrs.Resource{
   620  				Mode: addrs.ManagedResourceMode,
   621  				Type: "test_instance",
   622  				Name: "foo",
   623  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   624  			&states.ResourceInstanceObjectSrc{
   625  				AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
   626  				Status:    states.ObjectReady,
   627  			},
   628  			addrs.AbsProviderConfig{
   629  				Provider: addrs.NewDefaultProvider("test"),
   630  				Module:   addrs.RootModule,
   631  			},
   632  		)
   633  		s.SetResourceInstanceCurrent(
   634  			addrs.Resource{
   635  				Mode: addrs.ManagedResourceMode,
   636  				Type: "test_instance",
   637  				Name: "foo",
   638  			}.Instance(addrs.NoKey).Absolute(childModule),
   639  			&states.ResourceInstanceObjectSrc{
   640  				AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
   641  				Status:    states.ObjectReady,
   642  			},
   643  			addrs.AbsProviderConfig{
   644  				Provider: addrs.NewDefaultProvider("test"),
   645  				Module:   childModule.Module(),
   646  			},
   647  		)
   648  		s.SetResourceInstanceCurrent(
   649  			addrs.Resource{
   650  				Mode: addrs.ManagedResourceMode,
   651  				Type: "test_instance",
   652  				Name: "foo",
   653  			}.Instance(addrs.NoKey).Absolute(subModule),
   654  			&states.ResourceInstanceObjectSrc{
   655  				AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
   656  				Status:    states.ObjectReady,
   657  			},
   658  			addrs.AbsProviderConfig{
   659  				Provider: addrs.NewDefaultProvider("test"),
   660  				Module:   subModule.Module(),
   661  			},
   662  		)
   663  	})
   664  	moduleMap := make(map[string][]addrs.ModuleInstance)
   665  	moduleMap[""] = []addrs.ModuleInstance{childModule, subModule}
   666  
   667  	got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap)
   668  
   669  	if err != nil {
   670  		t.Fatalf("unexpected error: %s", err.Error())
   671  	}
   672  
   673  	if len(got) != 2 {
   674  		t.Fatalf("wrong result! got %d modules, expected 2", len(got))
   675  	}
   676  
   677  	if got[0].Address != "module.child" || got[1].Address != "module.submodule" {
   678  		t.Fatalf("wrong result! got %#v\n", got)
   679  	}
   680  
   681  }
   682  
   683  func TestMarshalModules_nested(t *testing.T) {
   684  	childModule, _ := addrs.ParseModuleInstanceStr("module.child")
   685  	subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule")
   686  	testState := states.BuildState(func(s *states.SyncState) {
   687  		s.SetResourceInstanceCurrent(
   688  			addrs.Resource{
   689  				Mode: addrs.ManagedResourceMode,
   690  				Type: "test_instance",
   691  				Name: "foo",
   692  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   693  			&states.ResourceInstanceObjectSrc{
   694  				AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
   695  				Status:    states.ObjectReady,
   696  			},
   697  			addrs.AbsProviderConfig{
   698  				Provider: addrs.NewDefaultProvider("test"),
   699  				Module:   addrs.RootModule,
   700  			},
   701  		)
   702  		s.SetResourceInstanceCurrent(
   703  			addrs.Resource{
   704  				Mode: addrs.ManagedResourceMode,
   705  				Type: "test_instance",
   706  				Name: "foo",
   707  			}.Instance(addrs.NoKey).Absolute(childModule),
   708  			&states.ResourceInstanceObjectSrc{
   709  				AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
   710  				Status:    states.ObjectReady,
   711  			},
   712  			addrs.AbsProviderConfig{
   713  				Provider: addrs.NewDefaultProvider("test"),
   714  				Module:   childModule.Module(),
   715  			},
   716  		)
   717  		s.SetResourceInstanceCurrent(
   718  			addrs.Resource{
   719  				Mode: addrs.ManagedResourceMode,
   720  				Type: "test_instance",
   721  				Name: "foo",
   722  			}.Instance(addrs.NoKey).Absolute(subModule),
   723  			&states.ResourceInstanceObjectSrc{
   724  				AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
   725  				Status:    states.ObjectReady,
   726  			},
   727  			addrs.AbsProviderConfig{
   728  				Provider: addrs.NewDefaultProvider("test"),
   729  				Module:   subModule.Module(),
   730  			},
   731  		)
   732  	})
   733  	moduleMap := make(map[string][]addrs.ModuleInstance)
   734  	moduleMap[""] = []addrs.ModuleInstance{childModule}
   735  	moduleMap[childModule.String()] = []addrs.ModuleInstance{subModule}
   736  
   737  	got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap)
   738  
   739  	if err != nil {
   740  		t.Fatalf("unexpected error: %s", err.Error())
   741  	}
   742  
   743  	if len(got) != 1 {
   744  		t.Fatalf("wrong result! got %d modules, expected 1", len(got))
   745  	}
   746  
   747  	if got[0].Address != "module.child" {
   748  		t.Fatalf("wrong result! got %#v\n", got)
   749  	}
   750  
   751  	if got[0].ChildModules[0].Address != "module.child.module.submodule" {
   752  		t.Fatalf("wrong result! got %#v\n", got)
   753  	}
   754  }
   755  
   756  func TestMarshalModules_parent_no_resources(t *testing.T) {
   757  	subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule")
   758  	testState := states.BuildState(func(s *states.SyncState) {
   759  		s.SetResourceInstanceCurrent(
   760  			addrs.Resource{
   761  				Mode: addrs.ManagedResourceMode,
   762  				Type: "test_instance",
   763  				Name: "foo",
   764  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   765  			&states.ResourceInstanceObjectSrc{
   766  				AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
   767  				Status:    states.ObjectReady,
   768  			},
   769  			addrs.AbsProviderConfig{
   770  				Provider: addrs.NewDefaultProvider("test"),
   771  				Module:   addrs.RootModule,
   772  			},
   773  		)
   774  		s.SetResourceInstanceCurrent(
   775  			addrs.Resource{
   776  				Mode: addrs.ManagedResourceMode,
   777  				Type: "test_instance",
   778  				Name: "foo",
   779  			}.Instance(addrs.NoKey).Absolute(subModule),
   780  			&states.ResourceInstanceObjectSrc{
   781  				AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
   782  				Status:    states.ObjectReady,
   783  			},
   784  			addrs.AbsProviderConfig{
   785  				Provider: addrs.NewDefaultProvider("test"),
   786  				Module:   subModule.Module(),
   787  			},
   788  		)
   789  	})
   790  	got, err := marshalRootModule(testState, testSchemas())
   791  
   792  	if err != nil {
   793  		t.Fatalf("unexpected error: %s", err.Error())
   794  	}
   795  
   796  	if len(got.ChildModules) != 1 {
   797  		t.Fatalf("wrong result! got %d modules, expected 1", len(got.ChildModules))
   798  	}
   799  
   800  	if got.ChildModules[0].Address != "module.child" {
   801  		t.Fatalf("wrong result! got %#v\n", got)
   802  	}
   803  
   804  	if got.ChildModules[0].ChildModules[0].Address != "module.child.module.submodule" {
   805  		t.Fatalf("wrong result! got %#v\n", got)
   806  	}
   807  }
   808  
   809  func testSchemas() *terraform.Schemas {
   810  	return &terraform.Schemas{
   811  		Providers: map[addrs.Provider]providers.ProviderSchema{
   812  			addrs.NewDefaultProvider("test"): {
   813  				ResourceTypes: map[string]providers.Schema{
   814  					"test_thing": {
   815  						Block: &configschema.Block{
   816  							Attributes: map[string]*configschema.Attribute{
   817  								"woozles": {Type: cty.String, Optional: true, Computed: true},
   818  								"foozles": {Type: cty.String, Optional: true, Sensitive: true},
   819  							},
   820  						},
   821  					},
   822  					"test_instance": {
   823  						Block: &configschema.Block{
   824  							Attributes: map[string]*configschema.Attribute{
   825  								"id":  {Type: cty.String, Optional: true, Computed: true},
   826  								"foo": {Type: cty.String, Optional: true},
   827  								"bar": {Type: cty.String, Optional: true},
   828  							},
   829  						},
   830  					},
   831  					"test_map_attr": {
   832  						Block: &configschema.Block{
   833  							Attributes: map[string]*configschema.Attribute{
   834  								"data": {Type: cty.Map(cty.String), Optional: true, Computed: true, Sensitive: true},
   835  							},
   836  						},
   837  					},
   838  				},
   839  			},
   840  		},
   841  	}
   842  }
   843  
   844  func TestSensitiveAsBool(t *testing.T) {
   845  	tests := []struct {
   846  		Input cty.Value
   847  		Want  cty.Value
   848  	}{
   849  		{
   850  			cty.StringVal("hello"),
   851  			cty.False,
   852  		},
   853  		{
   854  			cty.NullVal(cty.String),
   855  			cty.False,
   856  		},
   857  		{
   858  			cty.StringVal("hello").Mark(marks.Sensitive),
   859  			cty.True,
   860  		},
   861  		{
   862  			cty.NullVal(cty.String).Mark(marks.Sensitive),
   863  			cty.True,
   864  		},
   865  
   866  		{
   867  			cty.NullVal(cty.DynamicPseudoType).Mark(marks.Sensitive),
   868  			cty.True,
   869  		},
   870  		{
   871  			cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})),
   872  			cty.False,
   873  		},
   874  		{
   875  			cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})).Mark(marks.Sensitive),
   876  			cty.True,
   877  		},
   878  		{
   879  			cty.DynamicVal,
   880  			cty.False,
   881  		},
   882  		{
   883  			cty.DynamicVal.Mark(marks.Sensitive),
   884  			cty.True,
   885  		},
   886  
   887  		{
   888  			cty.ListValEmpty(cty.String),
   889  			cty.EmptyTupleVal,
   890  		},
   891  		{
   892  			cty.ListValEmpty(cty.String).Mark(marks.Sensitive),
   893  			cty.True,
   894  		},
   895  		{
   896  			cty.ListVal([]cty.Value{
   897  				cty.StringVal("hello"),
   898  				cty.StringVal("friend").Mark(marks.Sensitive),
   899  			}),
   900  			cty.TupleVal([]cty.Value{
   901  				cty.False,
   902  				cty.True,
   903  			}),
   904  		},
   905  		{
   906  			cty.SetValEmpty(cty.String),
   907  			cty.EmptyTupleVal,
   908  		},
   909  		{
   910  			cty.SetValEmpty(cty.String).Mark(marks.Sensitive),
   911  			cty.True,
   912  		},
   913  		{
   914  			cty.SetVal([]cty.Value{cty.StringVal("hello")}),
   915  			cty.TupleVal([]cty.Value{cty.False}),
   916  		},
   917  		{
   918  			cty.SetVal([]cty.Value{cty.StringVal("hello").Mark(marks.Sensitive)}),
   919  			cty.True,
   920  		},
   921  		{
   922  			cty.EmptyTupleVal.Mark(marks.Sensitive),
   923  			cty.True,
   924  		},
   925  		{
   926  			cty.TupleVal([]cty.Value{
   927  				cty.StringVal("hello"),
   928  				cty.StringVal("friend").Mark(marks.Sensitive),
   929  			}),
   930  			cty.TupleVal([]cty.Value{
   931  				cty.False,
   932  				cty.True,
   933  			}),
   934  		},
   935  		{
   936  			cty.MapValEmpty(cty.String),
   937  			cty.EmptyObjectVal,
   938  		},
   939  		{
   940  			cty.MapValEmpty(cty.String).Mark(marks.Sensitive),
   941  			cty.True,
   942  		},
   943  		{
   944  			cty.MapVal(map[string]cty.Value{
   945  				"greeting": cty.StringVal("hello"),
   946  				"animal":   cty.StringVal("horse"),
   947  			}),
   948  			cty.EmptyObjectVal,
   949  		},
   950  		{
   951  			cty.MapVal(map[string]cty.Value{
   952  				"greeting": cty.StringVal("hello"),
   953  				"animal":   cty.StringVal("horse").Mark(marks.Sensitive),
   954  			}),
   955  			cty.ObjectVal(map[string]cty.Value{
   956  				"animal": cty.True,
   957  			}),
   958  		},
   959  		{
   960  			cty.MapVal(map[string]cty.Value{
   961  				"greeting": cty.StringVal("hello"),
   962  				"animal":   cty.StringVal("horse").Mark(marks.Sensitive),
   963  			}).Mark(marks.Sensitive),
   964  			cty.True,
   965  		},
   966  		{
   967  			cty.EmptyObjectVal,
   968  			cty.EmptyObjectVal,
   969  		},
   970  		{
   971  			cty.ObjectVal(map[string]cty.Value{
   972  				"greeting": cty.StringVal("hello"),
   973  				"animal":   cty.StringVal("horse"),
   974  			}),
   975  			cty.EmptyObjectVal,
   976  		},
   977  		{
   978  			cty.ObjectVal(map[string]cty.Value{
   979  				"greeting": cty.StringVal("hello"),
   980  				"animal":   cty.StringVal("horse").Mark(marks.Sensitive),
   981  			}),
   982  			cty.ObjectVal(map[string]cty.Value{
   983  				"animal": cty.True,
   984  			}),
   985  		},
   986  		{
   987  			cty.ObjectVal(map[string]cty.Value{
   988  				"greeting": cty.StringVal("hello"),
   989  				"animal":   cty.StringVal("horse").Mark(marks.Sensitive),
   990  			}).Mark(marks.Sensitive),
   991  			cty.True,
   992  		},
   993  		{
   994  			cty.ListVal([]cty.Value{
   995  				cty.ObjectVal(map[string]cty.Value{
   996  					"a": cty.UnknownVal(cty.String),
   997  				}),
   998  				cty.ObjectVal(map[string]cty.Value{
   999  					"a": cty.StringVal("known").Mark(marks.Sensitive),
  1000  				}),
  1001  			}),
  1002  			cty.TupleVal([]cty.Value{
  1003  				cty.EmptyObjectVal,
  1004  				cty.ObjectVal(map[string]cty.Value{
  1005  					"a": cty.True,
  1006  				}),
  1007  			}),
  1008  		},
  1009  		{
  1010  			cty.ListVal([]cty.Value{
  1011  				cty.MapValEmpty(cty.String),
  1012  				cty.MapVal(map[string]cty.Value{
  1013  					"a": cty.StringVal("known").Mark(marks.Sensitive),
  1014  				}),
  1015  				cty.MapVal(map[string]cty.Value{
  1016  					"a": cty.UnknownVal(cty.String),
  1017  				}),
  1018  			}),
  1019  			cty.TupleVal([]cty.Value{
  1020  				cty.EmptyObjectVal,
  1021  				cty.ObjectVal(map[string]cty.Value{
  1022  					"a": cty.True,
  1023  				}),
  1024  				cty.EmptyObjectVal,
  1025  			}),
  1026  		},
  1027  		{
  1028  			cty.ObjectVal(map[string]cty.Value{
  1029  				"list":   cty.UnknownVal(cty.List(cty.String)),
  1030  				"set":    cty.UnknownVal(cty.Set(cty.Bool)),
  1031  				"tuple":  cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Number})),
  1032  				"map":    cty.UnknownVal(cty.Map(cty.String)),
  1033  				"object": cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})),
  1034  			}),
  1035  			cty.ObjectVal(map[string]cty.Value{
  1036  				"list":   cty.EmptyTupleVal,
  1037  				"set":    cty.EmptyTupleVal,
  1038  				"tuple":  cty.EmptyTupleVal,
  1039  				"map":    cty.EmptyObjectVal,
  1040  				"object": cty.EmptyObjectVal,
  1041  			}),
  1042  		},
  1043  	}
  1044  
  1045  	for _, test := range tests {
  1046  		got := SensitiveAsBool(test.Input)
  1047  		if !reflect.DeepEqual(got, test.Want) {
  1048  			t.Errorf(
  1049  				"wrong result\ninput: %#v\ngot:   %#v\nwant:  %#v",
  1050  				test.Input, got, test.Want,
  1051  			)
  1052  		}
  1053  	}
  1054  }