github.com/hugorut/terraform@v1.1.3/src/command/jsonstate/state_test.go (about)

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