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