github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/command/views/add_test.go (about)

     1  package views
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/configs/configschema"
    10  	"github.com/hashicorp/terraform/internal/lang/marks"
    11  	"github.com/hashicorp/terraform/internal/terminal"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  // The output is tested in greater detail in other tests; this suite focuses on
    16  // details specific to the Resource function.
    17  func TestAddResource(t *testing.T) {
    18  	t.Run("config only", func(t *testing.T) {
    19  		streams, done := terminal.StreamsForTesting(t)
    20  		v := addHuman{view: NewView(streams), optional: true}
    21  		err := v.Resource(
    22  			mustResourceInstanceAddr("test_instance.foo"),
    23  			addTestSchemaSensitive(configschema.NestingSingle),
    24  			addrs.NewDefaultLocalProviderConfig("mytest"), cty.NilVal,
    25  		)
    26  		if err != nil {
    27  			t.Fatal(err.Error())
    28  		}
    29  
    30  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
    31  # starting point for your resource configuration, with some limitations.
    32  #
    33  # The behavior of this command may change in future based on feedback, possibly
    34  # in incompatible ways. We don't recommend building automation around this
    35  # command at this time. If you have feedback about this command, please open
    36  # a feature request issue in the Terraform GitHub repository.
    37  resource "test_instance" "foo" {
    38    provider = mytest
    39  
    40    ami = null           # OPTIONAL string
    41    disks = {            # OPTIONAL object
    42      mount_point = null # OPTIONAL string
    43      size        = null # OPTIONAL string
    44    }
    45    id = null            # OPTIONAL string
    46    root_block_device {  # OPTIONAL block
    47      volume_type = null # OPTIONAL string
    48    }
    49  }
    50  `
    51  		output := done(t)
    52  		if output.Stdout() != expected {
    53  			t.Errorf("wrong result: %s", cmp.Diff(expected, output.Stdout()))
    54  		}
    55  	})
    56  
    57  	t.Run("from state", func(t *testing.T) {
    58  		streams, done := terminal.StreamsForTesting(t)
    59  		v := addHuman{view: NewView(streams), optional: true}
    60  		val := cty.ObjectVal(map[string]cty.Value{
    61  			"ami": cty.StringVal("ami-123456789"),
    62  			"disks": cty.ObjectVal(map[string]cty.Value{
    63  				"mount_point": cty.StringVal("/mnt/foo"),
    64  				"size":        cty.StringVal("50GB"),
    65  			}),
    66  		})
    67  
    68  		err := v.Resource(
    69  			mustResourceInstanceAddr("test_instance.foo"),
    70  			addTestSchemaSensitive(configschema.NestingSingle),
    71  			addrs.NewDefaultLocalProviderConfig("mytest"), val,
    72  		)
    73  		if err != nil {
    74  			t.Fatal(err.Error())
    75  		}
    76  
    77  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
    78  # starting point for your resource configuration, with some limitations.
    79  #
    80  # The behavior of this command may change in future based on feedback, possibly
    81  # in incompatible ways. We don't recommend building automation around this
    82  # command at this time. If you have feedback about this command, please open
    83  # a feature request issue in the Terraform GitHub repository.
    84  resource "test_instance" "foo" {
    85    provider = mytest
    86  
    87    ami   = "ami-123456789"
    88    disks = {} # sensitive
    89    id    = null
    90  }
    91  `
    92  		output := done(t)
    93  		if output.Stdout() != expected {
    94  			t.Errorf("wrong result: %s", cmp.Diff(expected, output.Stdout()))
    95  		}
    96  	})
    97  
    98  }
    99  
   100  func TestAdd_writeConfigAttributes(t *testing.T) {
   101  	tests := map[string]struct {
   102  		attrs    map[string]*configschema.Attribute
   103  		expected string
   104  	}{
   105  		"empty returns nil": {
   106  			map[string]*configschema.Attribute{},
   107  			"",
   108  		},
   109  		"attributes": {
   110  			map[string]*configschema.Attribute{
   111  				"ami": {
   112  					Type:     cty.Number,
   113  					Required: true,
   114  				},
   115  				"boot_disk": {
   116  					Type:     cty.String,
   117  					Optional: true,
   118  				},
   119  				"password": {
   120  					Type:      cty.String,
   121  					Optional:  true,
   122  					Sensitive: true, // sensitivity is ignored when printing blank templates
   123  				},
   124  			},
   125  			`ami = null # REQUIRED number
   126  boot_disk = null # OPTIONAL string
   127  password = null # OPTIONAL string
   128  `,
   129  		},
   130  		"attributes with nested types": {
   131  			map[string]*configschema.Attribute{
   132  				"ami": {
   133  					Type:     cty.Number,
   134  					Required: true,
   135  				},
   136  				"disks": {
   137  					NestedType: &configschema.Object{
   138  						Nesting: configschema.NestingSingle,
   139  						Attributes: map[string]*configschema.Attribute{
   140  							"size": {
   141  								Type:     cty.Number,
   142  								Optional: true,
   143  							},
   144  							"mount_point": {
   145  								Type:     cty.String,
   146  								Required: true,
   147  							},
   148  						},
   149  					},
   150  					Optional: true,
   151  				},
   152  			},
   153  			`ami = null # REQUIRED number
   154  disks = { # OPTIONAL object
   155    mount_point = null # REQUIRED string
   156    size = null # OPTIONAL number
   157  }
   158  `,
   159  		},
   160  	}
   161  
   162  	v := addHuman{optional: true}
   163  
   164  	for name, test := range tests {
   165  		t.Run(name, func(t *testing.T) {
   166  			var buf strings.Builder
   167  			if err := v.writeConfigAttributes(&buf, test.attrs, 0); err != nil {
   168  				t.Errorf("unexpected error")
   169  			}
   170  			if buf.String() != test.expected {
   171  				t.Errorf("wrong result: %s", cmp.Diff(test.expected, buf.String()))
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestAdd_writeConfigAttributesFromExisting(t *testing.T) {
   178  	attrs := map[string]*configschema.Attribute{
   179  		"ami": {
   180  			Type:     cty.Number,
   181  			Required: true,
   182  		},
   183  		"boot_disk": {
   184  			Type:     cty.String,
   185  			Optional: true,
   186  		},
   187  		"password": {
   188  			Type:      cty.String,
   189  			Optional:  true,
   190  			Sensitive: true,
   191  		},
   192  		"disks": {
   193  			NestedType: &configschema.Object{
   194  				Nesting: configschema.NestingSingle,
   195  				Attributes: map[string]*configschema.Attribute{
   196  					"size": {
   197  						Type:     cty.Number,
   198  						Optional: true,
   199  					},
   200  					"mount_point": {
   201  						Type:     cty.String,
   202  						Required: true,
   203  					},
   204  				},
   205  			},
   206  			Optional: true,
   207  		},
   208  	}
   209  
   210  	tests := map[string]struct {
   211  		attrs    map[string]*configschema.Attribute
   212  		val      cty.Value
   213  		expected string
   214  	}{
   215  		"empty returns nil": {
   216  			map[string]*configschema.Attribute{},
   217  			cty.NilVal,
   218  			"",
   219  		},
   220  		"mixed attributes": {
   221  			attrs,
   222  			cty.ObjectVal(map[string]cty.Value{
   223  				"ami":       cty.NumberIntVal(123456),
   224  				"boot_disk": cty.NullVal(cty.String),
   225  				"password":  cty.StringVal("i am secret"),
   226  				"disks": cty.ObjectVal(map[string]cty.Value{
   227  					"size":        cty.NumberIntVal(50),
   228  					"mount_point": cty.NullVal(cty.String),
   229  				}),
   230  			}),
   231  			`ami = 123456
   232  boot_disk = null
   233  disks = {
   234    mount_point = null
   235    size = 50
   236  }
   237  password = null # sensitive
   238  `,
   239  		},
   240  	}
   241  
   242  	v := addHuman{optional: true}
   243  
   244  	for name, test := range tests {
   245  		t.Run(name, func(t *testing.T) {
   246  			var buf strings.Builder
   247  			if err := v.writeConfigAttributesFromExisting(&buf, test.val, test.attrs, 0); err != nil {
   248  				t.Errorf("unexpected error")
   249  			}
   250  			if buf.String() != test.expected {
   251  				t.Errorf("wrong result: %s", cmp.Diff(test.expected, buf.String()))
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func TestAdd_writeConfigBlocks(t *testing.T) {
   258  	t.Run("NestingSingle", func(t *testing.T) {
   259  		v := addHuman{optional: true}
   260  		schema := addTestSchema(configschema.NestingSingle)
   261  		var buf strings.Builder
   262  		v.writeConfigBlocks(&buf, schema.BlockTypes, 0)
   263  
   264  		expected := `network_rules { # REQUIRED block
   265    ip_address = null # OPTIONAL string
   266  }
   267  root_block_device { # OPTIONAL block
   268    volume_type = null # OPTIONAL string
   269  }
   270  `
   271  
   272  		if !cmp.Equal(buf.String(), expected) {
   273  			t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   274  		}
   275  	})
   276  
   277  	t.Run("NestingList", func(t *testing.T) {
   278  		v := addHuman{optional: true}
   279  		schema := addTestSchema(configschema.NestingList)
   280  		var buf strings.Builder
   281  		v.writeConfigBlocks(&buf, schema.BlockTypes, 0)
   282  
   283  		expected := `network_rules { # REQUIRED block
   284    ip_address = null # OPTIONAL string
   285  }
   286  root_block_device { # OPTIONAL block
   287    volume_type = null # OPTIONAL string
   288  }
   289  `
   290  
   291  		if !cmp.Equal(buf.String(), expected) {
   292  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   293  		}
   294  	})
   295  
   296  	t.Run("NestingSet", func(t *testing.T) {
   297  		v := addHuman{optional: true}
   298  		schema := addTestSchema(configschema.NestingSet)
   299  		var buf strings.Builder
   300  		v.writeConfigBlocks(&buf, schema.BlockTypes, 0)
   301  
   302  		expected := `network_rules { # REQUIRED block
   303    ip_address = null # OPTIONAL string
   304  }
   305  root_block_device { # OPTIONAL block
   306    volume_type = null # OPTIONAL string
   307  }
   308  `
   309  
   310  		if !cmp.Equal(buf.String(), expected) {
   311  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   312  		}
   313  	})
   314  
   315  	t.Run("NestingMap", func(t *testing.T) {
   316  		v := addHuman{optional: true}
   317  		schema := addTestSchema(configschema.NestingMap)
   318  		var buf strings.Builder
   319  		v.writeConfigBlocks(&buf, schema.BlockTypes, 0)
   320  
   321  		expected := `network_rules "key" { # REQUIRED block
   322    ip_address = null # OPTIONAL string
   323  }
   324  root_block_device "key" { # OPTIONAL block
   325    volume_type = null # OPTIONAL string
   326  }
   327  `
   328  
   329  		if !cmp.Equal(buf.String(), expected) {
   330  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   331  		}
   332  	})
   333  }
   334  
   335  func TestAdd_writeConfigBlocksFromExisting(t *testing.T) {
   336  
   337  	t.Run("NestingSingle", func(t *testing.T) {
   338  		v := addHuman{optional: true}
   339  		val := cty.ObjectVal(map[string]cty.Value{
   340  			"root_block_device": cty.ObjectVal(map[string]cty.Value{
   341  				"volume_type": cty.StringVal("foo"),
   342  			}),
   343  		})
   344  		schema := addTestSchema(configschema.NestingSingle)
   345  		var buf strings.Builder
   346  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   347  
   348  		expected := `root_block_device {
   349    volume_type = "foo"
   350  }
   351  `
   352  
   353  		if !cmp.Equal(buf.String(), expected) {
   354  			t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   355  		}
   356  	})
   357  
   358  	t.Run("NestingSingle_marked_attr", func(t *testing.T) {
   359  		v := addHuman{optional: true}
   360  		val := cty.ObjectVal(map[string]cty.Value{
   361  			"root_block_device": cty.ObjectVal(map[string]cty.Value{
   362  				"volume_type": cty.StringVal("foo").Mark(marks.Sensitive),
   363  			}),
   364  		})
   365  		schema := addTestSchema(configschema.NestingSingle)
   366  		var buf strings.Builder
   367  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   368  
   369  		expected := `root_block_device {
   370    volume_type = null # sensitive
   371  }
   372  `
   373  
   374  		if !cmp.Equal(buf.String(), expected) {
   375  			t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   376  		}
   377  	})
   378  
   379  	t.Run("NestingSingle_entirely_marked", func(t *testing.T) {
   380  		v := addHuman{optional: true}
   381  		val := cty.ObjectVal(map[string]cty.Value{
   382  			"root_block_device": cty.ObjectVal(map[string]cty.Value{
   383  				"volume_type": cty.StringVal("foo"),
   384  			}),
   385  		}).Mark(marks.Sensitive)
   386  		schema := addTestSchema(configschema.NestingSingle)
   387  		var buf strings.Builder
   388  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   389  
   390  		expected := `root_block_device {} # sensitive
   391  `
   392  
   393  		if !cmp.Equal(buf.String(), expected) {
   394  			t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   395  		}
   396  	})
   397  
   398  	t.Run("NestingList", func(t *testing.T) {
   399  		v := addHuman{optional: true}
   400  		val := cty.ObjectVal(map[string]cty.Value{
   401  			"root_block_device": cty.ListVal([]cty.Value{
   402  				cty.ObjectVal(map[string]cty.Value{
   403  					"volume_type": cty.StringVal("foo"),
   404  				}),
   405  				cty.ObjectVal(map[string]cty.Value{
   406  					"volume_type": cty.StringVal("bar"),
   407  				}),
   408  			}),
   409  		})
   410  		schema := addTestSchema(configschema.NestingList)
   411  		var buf strings.Builder
   412  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   413  
   414  		expected := `root_block_device {
   415    volume_type = "foo"
   416  }
   417  root_block_device {
   418    volume_type = "bar"
   419  }
   420  `
   421  
   422  		if !cmp.Equal(buf.String(), expected) {
   423  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   424  		}
   425  	})
   426  
   427  	t.Run("NestingList_marked_attr", func(t *testing.T) {
   428  		v := addHuman{optional: true}
   429  		val := cty.ObjectVal(map[string]cty.Value{
   430  			"root_block_device": cty.ListVal([]cty.Value{
   431  				cty.ObjectVal(map[string]cty.Value{
   432  					"volume_type": cty.StringVal("foo").Mark(marks.Sensitive),
   433  				}),
   434  				cty.ObjectVal(map[string]cty.Value{
   435  					"volume_type": cty.StringVal("bar"),
   436  				}),
   437  			}),
   438  		})
   439  		schema := addTestSchema(configschema.NestingList)
   440  		var buf strings.Builder
   441  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   442  
   443  		expected := `root_block_device {
   444    volume_type = null # sensitive
   445  }
   446  root_block_device {
   447    volume_type = "bar"
   448  }
   449  `
   450  
   451  		if !cmp.Equal(buf.String(), expected) {
   452  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   453  		}
   454  	})
   455  
   456  	t.Run("NestingList_entirely_marked", func(t *testing.T) {
   457  		v := addHuman{optional: true}
   458  		val := cty.ObjectVal(map[string]cty.Value{
   459  			"root_block_device": cty.ListVal([]cty.Value{
   460  				cty.ObjectVal(map[string]cty.Value{
   461  					"volume_type": cty.StringVal("foo"),
   462  				}),
   463  				cty.ObjectVal(map[string]cty.Value{
   464  					"volume_type": cty.StringVal("bar"),
   465  				}),
   466  			}).Mark(marks.Sensitive),
   467  		})
   468  		schema := addTestSchema(configschema.NestingList)
   469  		var buf strings.Builder
   470  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   471  
   472  		expected := `root_block_device {} # sensitive
   473  `
   474  
   475  		if !cmp.Equal(buf.String(), expected) {
   476  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   477  		}
   478  	})
   479  
   480  	t.Run("NestingSet", func(t *testing.T) {
   481  		v := addHuman{optional: true}
   482  		val := cty.ObjectVal(map[string]cty.Value{
   483  			"root_block_device": cty.SetVal([]cty.Value{
   484  				cty.ObjectVal(map[string]cty.Value{
   485  					"volume_type": cty.StringVal("foo"),
   486  				}),
   487  				cty.ObjectVal(map[string]cty.Value{
   488  					"volume_type": cty.StringVal("bar"),
   489  				}),
   490  			}),
   491  		})
   492  		schema := addTestSchema(configschema.NestingSet)
   493  		var buf strings.Builder
   494  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   495  
   496  		expected := `root_block_device {
   497    volume_type = "bar"
   498  }
   499  root_block_device {
   500    volume_type = "foo"
   501  }
   502  `
   503  
   504  		if !cmp.Equal(buf.String(), expected) {
   505  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   506  		}
   507  	})
   508  
   509  	t.Run("NestingSet_marked", func(t *testing.T) {
   510  		v := addHuman{optional: true}
   511  		// In cty.Sets, the entire set ends up marked if any element is marked.
   512  		val := cty.ObjectVal(map[string]cty.Value{
   513  			"root_block_device": cty.SetVal([]cty.Value{
   514  				cty.ObjectVal(map[string]cty.Value{
   515  					"volume_type": cty.StringVal("foo"),
   516  				}),
   517  				cty.ObjectVal(map[string]cty.Value{
   518  					"volume_type": cty.StringVal("bar"),
   519  				}),
   520  			}).Mark(marks.Sensitive),
   521  		})
   522  		schema := addTestSchema(configschema.NestingSet)
   523  		var buf strings.Builder
   524  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   525  
   526  		// When the entire set of blocks is sensitive, we only print one block.
   527  		expected := `root_block_device {} # sensitive
   528  `
   529  
   530  		if !cmp.Equal(buf.String(), expected) {
   531  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   532  		}
   533  	})
   534  
   535  	t.Run("NestingMap", func(t *testing.T) {
   536  		v := addHuman{optional: true}
   537  		val := cty.ObjectVal(map[string]cty.Value{
   538  			"root_block_device": cty.MapVal(map[string]cty.Value{
   539  				"1": cty.ObjectVal(map[string]cty.Value{
   540  					"volume_type": cty.StringVal("foo"),
   541  				}),
   542  				"2": cty.ObjectVal(map[string]cty.Value{
   543  					"volume_type": cty.StringVal("bar"),
   544  				}),
   545  			}),
   546  		})
   547  		schema := addTestSchema(configschema.NestingMap)
   548  		var buf strings.Builder
   549  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   550  
   551  		expected := `root_block_device "1" {
   552    volume_type = "foo"
   553  }
   554  root_block_device "2" {
   555    volume_type = "bar"
   556  }
   557  `
   558  
   559  		if !cmp.Equal(buf.String(), expected) {
   560  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   561  		}
   562  	})
   563  
   564  	t.Run("NestingMap_marked", func(t *testing.T) {
   565  		v := addHuman{optional: true}
   566  		val := cty.ObjectVal(map[string]cty.Value{
   567  			"root_block_device": cty.MapVal(map[string]cty.Value{
   568  				"1": cty.ObjectVal(map[string]cty.Value{
   569  					"volume_type": cty.StringVal("foo").Mark(marks.Sensitive),
   570  				}),
   571  				"2": cty.ObjectVal(map[string]cty.Value{
   572  					"volume_type": cty.StringVal("bar"),
   573  				}),
   574  			}),
   575  		})
   576  		schema := addTestSchema(configschema.NestingMap)
   577  		var buf strings.Builder
   578  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   579  
   580  		expected := `root_block_device "1" {
   581    volume_type = null # sensitive
   582  }
   583  root_block_device "2" {
   584    volume_type = "bar"
   585  }
   586  `
   587  
   588  		if !cmp.Equal(buf.String(), expected) {
   589  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   590  		}
   591  	})
   592  
   593  	t.Run("NestingMap_entirely_marked", func(t *testing.T) {
   594  		v := addHuman{optional: true}
   595  		val := cty.ObjectVal(map[string]cty.Value{
   596  			"root_block_device": cty.MapVal(map[string]cty.Value{
   597  				"1": cty.ObjectVal(map[string]cty.Value{
   598  					"volume_type": cty.StringVal("foo"),
   599  				}),
   600  				"2": cty.ObjectVal(map[string]cty.Value{
   601  					"volume_type": cty.StringVal("bar"),
   602  				}),
   603  			}).Mark(marks.Sensitive),
   604  		})
   605  		schema := addTestSchema(configschema.NestingMap)
   606  		var buf strings.Builder
   607  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   608  
   609  		expected := `root_block_device {} # sensitive
   610  `
   611  
   612  		if !cmp.Equal(buf.String(), expected) {
   613  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   614  		}
   615  	})
   616  
   617  	t.Run("NestingMap_marked_elem", func(t *testing.T) {
   618  		v := addHuman{optional: true}
   619  		val := cty.ObjectVal(map[string]cty.Value{
   620  			"root_block_device": cty.MapVal(map[string]cty.Value{
   621  				"1": cty.ObjectVal(map[string]cty.Value{
   622  					"volume_type": cty.StringVal("foo"),
   623  				}),
   624  				"2": cty.ObjectVal(map[string]cty.Value{
   625  					"volume_type": cty.StringVal("bar"),
   626  				}).Mark(marks.Sensitive),
   627  			}),
   628  		})
   629  		schema := addTestSchema(configschema.NestingMap)
   630  		var buf strings.Builder
   631  		v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0)
   632  
   633  		expected := `root_block_device "1" {
   634    volume_type = "foo"
   635  }
   636  root_block_device "2" {} # sensitive
   637  `
   638  
   639  		if !cmp.Equal(buf.String(), expected) {
   640  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   641  		}
   642  	})
   643  }
   644  
   645  func TestAdd_writeConfigNestedTypeAttribute(t *testing.T) {
   646  	t.Run("NestingSingle", func(t *testing.T) {
   647  		v := addHuman{optional: true}
   648  		schema := addTestSchema(configschema.NestingSingle)
   649  		var buf strings.Builder
   650  		v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0)
   651  
   652  		expected := `disks = { # OPTIONAL object
   653    mount_point = null # OPTIONAL string
   654    size = null # OPTIONAL string
   655  }
   656  `
   657  
   658  		if !cmp.Equal(buf.String(), expected) {
   659  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   660  		}
   661  	})
   662  
   663  	t.Run("NestingList", func(t *testing.T) {
   664  		v := addHuman{optional: true}
   665  		schema := addTestSchema(configschema.NestingList)
   666  		var buf strings.Builder
   667  		v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0)
   668  
   669  		expected := `disks = [{ # OPTIONAL list of object
   670    mount_point = null # OPTIONAL string
   671    size = null # OPTIONAL string
   672  }]
   673  `
   674  
   675  		if !cmp.Equal(buf.String(), expected) {
   676  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   677  		}
   678  	})
   679  
   680  	t.Run("NestingSet", func(t *testing.T) {
   681  		v := addHuman{optional: true}
   682  		schema := addTestSchema(configschema.NestingSet)
   683  		var buf strings.Builder
   684  		v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0)
   685  
   686  		expected := `disks = [{ # OPTIONAL set of object
   687    mount_point = null # OPTIONAL string
   688    size = null # OPTIONAL string
   689  }]
   690  `
   691  
   692  		if !cmp.Equal(buf.String(), expected) {
   693  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   694  		}
   695  	})
   696  
   697  	t.Run("NestingMap", func(t *testing.T) {
   698  		v := addHuman{optional: true}
   699  		schema := addTestSchema(configschema.NestingMap)
   700  		var buf strings.Builder
   701  		v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0)
   702  
   703  		expected := `disks = { # OPTIONAL map of object
   704    key = {
   705      mount_point = null # OPTIONAL string
   706      size = null # OPTIONAL string
   707    }
   708  }
   709  `
   710  
   711  		if !cmp.Equal(buf.String(), expected) {
   712  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   713  		}
   714  	})
   715  }
   716  
   717  func TestAdd_WriteConfigNestedTypeAttributeFromExisting(t *testing.T) {
   718  	t.Run("NestingSingle", func(t *testing.T) {
   719  		v := addHuman{optional: true}
   720  		val := cty.ObjectVal(map[string]cty.Value{
   721  			"disks": cty.ObjectVal(map[string]cty.Value{
   722  				"mount_point": cty.StringVal("/mnt/foo"),
   723  				"size":        cty.StringVal("50GB"),
   724  			}),
   725  		})
   726  		schema := addTestSchema(configschema.NestingSingle)
   727  		var buf strings.Builder
   728  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   729  
   730  		expected := `disks = {
   731    mount_point = "/mnt/foo"
   732    size = "50GB"
   733  }
   734  `
   735  
   736  		if !cmp.Equal(buf.String(), expected) {
   737  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   738  		}
   739  	})
   740  
   741  	t.Run("NestingSingle_sensitive", func(t *testing.T) {
   742  		v := addHuman{optional: true}
   743  		val := cty.ObjectVal(map[string]cty.Value{
   744  			"disks": cty.ObjectVal(map[string]cty.Value{
   745  				"mount_point": cty.StringVal("/mnt/foo"),
   746  				"size":        cty.StringVal("50GB"),
   747  			}),
   748  		})
   749  		schema := addTestSchemaSensitive(configschema.NestingSingle)
   750  		var buf strings.Builder
   751  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   752  
   753  		expected := `disks = {} # sensitive
   754  `
   755  
   756  		if !cmp.Equal(buf.String(), expected) {
   757  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   758  		}
   759  	})
   760  
   761  	t.Run("NestingList", func(t *testing.T) {
   762  		v := addHuman{optional: true}
   763  		val := cty.ObjectVal(map[string]cty.Value{
   764  			"disks": cty.ListVal([]cty.Value{
   765  				cty.ObjectVal(map[string]cty.Value{
   766  					"mount_point": cty.StringVal("/mnt/foo"),
   767  					"size":        cty.StringVal("50GB"),
   768  				}),
   769  				cty.ObjectVal(map[string]cty.Value{
   770  					"mount_point": cty.StringVal("/mnt/bar"),
   771  					"size":        cty.StringVal("250GB"),
   772  				}),
   773  			}),
   774  		})
   775  
   776  		schema := addTestSchema(configschema.NestingList)
   777  		var buf strings.Builder
   778  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   779  
   780  		expected := `disks = [
   781    {
   782      mount_point = "/mnt/foo"
   783      size = "50GB"
   784    },
   785    {
   786      mount_point = "/mnt/bar"
   787      size = "250GB"
   788    },
   789  ]
   790  `
   791  
   792  		if !cmp.Equal(buf.String(), expected) {
   793  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   794  		}
   795  	})
   796  
   797  	t.Run("NestingList - marked", func(t *testing.T) {
   798  		v := addHuman{optional: true}
   799  		val := cty.ObjectVal(map[string]cty.Value{
   800  			"disks": cty.ListVal([]cty.Value{
   801  				cty.ObjectVal(map[string]cty.Value{
   802  					"mount_point": cty.StringVal("/mnt/foo"),
   803  					"size":        cty.StringVal("50GB").Mark(marks.Sensitive),
   804  				}),
   805  				// This is an odd example, where the entire element is marked.
   806  				cty.ObjectVal(map[string]cty.Value{
   807  					"mount_point": cty.StringVal("/mnt/bar"),
   808  					"size":        cty.StringVal("250GB"),
   809  				}).Mark(marks.Sensitive),
   810  			}),
   811  		})
   812  
   813  		schema := addTestSchema(configschema.NestingList)
   814  		var buf strings.Builder
   815  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   816  
   817  		expected := `disks = [
   818    {
   819      mount_point = "/mnt/foo"
   820      size = null # sensitive
   821    },
   822    {}, # sensitive
   823  ]
   824  `
   825  
   826  		if !cmp.Equal(buf.String(), expected) {
   827  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   828  		}
   829  	})
   830  
   831  	t.Run("NestingList - entirely marked", func(t *testing.T) {
   832  		v := addHuman{optional: true}
   833  		val := cty.ObjectVal(map[string]cty.Value{
   834  			"disks": cty.ListVal([]cty.Value{
   835  				cty.ObjectVal(map[string]cty.Value{
   836  					"mount_point": cty.StringVal("/mnt/foo"),
   837  					"size":        cty.StringVal("50GB"),
   838  				}),
   839  				// This is an odd example, where the entire element is marked.
   840  				cty.ObjectVal(map[string]cty.Value{
   841  					"mount_point": cty.StringVal("/mnt/bar"),
   842  					"size":        cty.StringVal("250GB"),
   843  				}),
   844  			}),
   845  		}).Mark(marks.Sensitive)
   846  
   847  		schema := addTestSchema(configschema.NestingList)
   848  		var buf strings.Builder
   849  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   850  
   851  		expected := `disks = [] # sensitive
   852  `
   853  
   854  		if !cmp.Equal(buf.String(), expected) {
   855  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   856  		}
   857  	})
   858  
   859  	t.Run("NestingMap", func(t *testing.T) {
   860  		v := addHuman{optional: true}
   861  		val := cty.ObjectVal(map[string]cty.Value{
   862  			"disks": cty.MapVal(map[string]cty.Value{
   863  				"foo": cty.ObjectVal(map[string]cty.Value{
   864  					"mount_point": cty.StringVal("/mnt/foo"),
   865  					"size":        cty.StringVal("50GB"),
   866  				}),
   867  				"bar": cty.ObjectVal(map[string]cty.Value{
   868  					"mount_point": cty.StringVal("/mnt/bar"),
   869  					"size":        cty.StringVal("250GB"),
   870  				}),
   871  			}),
   872  		})
   873  		schema := addTestSchema(configschema.NestingMap)
   874  		var buf strings.Builder
   875  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   876  
   877  		expected := `disks = {
   878    bar = {
   879      mount_point = "/mnt/bar"
   880      size = "250GB"
   881    }
   882    foo = {
   883      mount_point = "/mnt/foo"
   884      size = "50GB"
   885    }
   886  }
   887  `
   888  
   889  		if !cmp.Equal(buf.String(), expected) {
   890  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   891  		}
   892  	})
   893  
   894  	t.Run("NestingMap - marked", func(t *testing.T) {
   895  		v := addHuman{optional: true}
   896  		val := cty.ObjectVal(map[string]cty.Value{
   897  			"disks": cty.MapVal(map[string]cty.Value{
   898  				"foo": cty.ObjectVal(map[string]cty.Value{
   899  					"mount_point": cty.StringVal("/mnt/foo"),
   900  					"size":        cty.StringVal("50GB").Mark(marks.Sensitive),
   901  				}),
   902  				"bar": cty.ObjectVal(map[string]cty.Value{
   903  					"mount_point": cty.StringVal("/mnt/bar"),
   904  					"size":        cty.StringVal("250GB"),
   905  				}).Mark(marks.Sensitive),
   906  			}),
   907  		})
   908  		schema := addTestSchema(configschema.NestingMap)
   909  		var buf strings.Builder
   910  		v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0)
   911  
   912  		expected := `disks = {
   913    bar = {} # sensitive
   914    foo = {
   915      mount_point = "/mnt/foo"
   916      size = null # sensitive
   917    }
   918  }
   919  `
   920  
   921  		if !cmp.Equal(buf.String(), expected) {
   922  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String()))
   923  		}
   924  	})
   925  }
   926  
   927  func addTestSchema(nesting configschema.NestingMode) *configschema.Block {
   928  	return &configschema.Block{
   929  		Attributes: map[string]*configschema.Attribute{
   930  			"id": {Type: cty.String, Optional: true, Computed: true},
   931  			// Attributes which are neither optional nor required should not print.
   932  			"uuid": {Type: cty.String, Computed: true},
   933  			"ami":  {Type: cty.String, Optional: true},
   934  			"disks": {
   935  				NestedType: &configschema.Object{
   936  					Attributes: map[string]*configschema.Attribute{
   937  						"mount_point": {Type: cty.String, Optional: true},
   938  						"size":        {Type: cty.String, Optional: true},
   939  					},
   940  					Nesting: nesting,
   941  				},
   942  			},
   943  		},
   944  		BlockTypes: map[string]*configschema.NestedBlock{
   945  			"root_block_device": {
   946  				Block: configschema.Block{
   947  					Attributes: map[string]*configschema.Attribute{
   948  						"volume_type": {
   949  							Type:     cty.String,
   950  							Optional: true,
   951  							Computed: true,
   952  						},
   953  					},
   954  				},
   955  				Nesting: nesting,
   956  			},
   957  			"network_rules": {
   958  				Block: configschema.Block{
   959  					Attributes: map[string]*configschema.Attribute{
   960  						"ip_address": {
   961  							Type:     cty.String,
   962  							Optional: true,
   963  							Computed: true,
   964  						},
   965  					},
   966  				},
   967  				Nesting:  nesting,
   968  				MinItems: 1,
   969  			},
   970  		},
   971  	}
   972  }
   973  
   974  // addTestSchemaSensitive returns a schema with a sensitive NestedType and a
   975  // NestedBlock with sensitive attributes.
   976  func addTestSchemaSensitive(nesting configschema.NestingMode) *configschema.Block {
   977  	return &configschema.Block{
   978  		Attributes: map[string]*configschema.Attribute{
   979  			"id": {Type: cty.String, Optional: true, Computed: true},
   980  			// Attributes which are neither optional nor required should not print.
   981  			"uuid": {Type: cty.String, Computed: true},
   982  			"ami":  {Type: cty.String, Optional: true},
   983  			"disks": {
   984  				NestedType: &configschema.Object{
   985  					Attributes: map[string]*configschema.Attribute{
   986  						"mount_point": {Type: cty.String, Optional: true},
   987  						"size":        {Type: cty.String, Optional: true},
   988  					},
   989  					Nesting: nesting,
   990  				},
   991  				Sensitive: true,
   992  			},
   993  		},
   994  		BlockTypes: map[string]*configschema.NestedBlock{
   995  			"root_block_device": {
   996  				Block: configschema.Block{
   997  					Attributes: map[string]*configschema.Attribute{
   998  						"volume_type": {
   999  							Type:      cty.String,
  1000  							Optional:  true,
  1001  							Computed:  true,
  1002  							Sensitive: true,
  1003  						},
  1004  					},
  1005  				},
  1006  				Nesting: nesting,
  1007  			},
  1008  		},
  1009  	}
  1010  }
  1011  
  1012  func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance {
  1013  	addr, diags := addrs.ParseAbsResourceInstanceStr(s)
  1014  	if diags.HasErrors() {
  1015  		panic(diags.Err())
  1016  	}
  1017  	return addr
  1018  }