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