kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/add_test.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"kubeform.dev/terraform-backend-sdk/addrs"
    12  	"kubeform.dev/terraform-backend-sdk/configs/configschema"
    13  	"kubeform.dev/terraform-backend-sdk/providers"
    14  	"kubeform.dev/terraform-backend-sdk/states"
    15  	"github.com/mitchellh/cli"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  // simple test cases with a simple resource schema
    20  func TestAdd_basic(t *testing.T) {
    21  	td := tempDir(t)
    22  	testCopyDir(t, testFixturePath("add/basic"), td)
    23  	defer os.RemoveAll(td)
    24  	defer testChdir(t, td)()
    25  
    26  	p := testProvider()
    27  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    28  		ResourceTypes: map[string]providers.Schema{
    29  			"test_instance": {
    30  				Block: &configschema.Block{
    31  					Attributes: map[string]*configschema.Attribute{
    32  						"id":    {Type: cty.String, Optional: true, Computed: true},
    33  						"ami":   {Type: cty.String, Optional: true, Description: "the ami to use"},
    34  						"value": {Type: cty.String, Required: true, Description: "a value of a thing"},
    35  					},
    36  				},
    37  			},
    38  		},
    39  	}
    40  
    41  	overrides := &testingOverrides{
    42  		Providers: map[addrs.Provider]providers.Factory{
    43  			addrs.NewDefaultProvider("test"):                                providers.FactoryFixed(p),
    44  			addrs.NewProvider("registry.terraform.io", "happycorp", "test"): providers.FactoryFixed(p),
    45  		},
    46  	}
    47  
    48  	t.Run("basic", func(t *testing.T) {
    49  		view, done := testView(t)
    50  		c := &AddCommand{
    51  			Meta: Meta{
    52  				testingOverrides: overrides,
    53  				View:             view,
    54  			},
    55  		}
    56  		args := []string{"test_instance.new"}
    57  		code := c.Run(args)
    58  		output := done(t)
    59  		if code != 0 {
    60  			fmt.Println(output.Stderr())
    61  			t.Fatalf("wrong exit status. Got %d, want 0", code)
    62  		}
    63  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
    64  # starting point for your resource configuration, with some limitations.
    65  #
    66  # The behavior of this command may change in future based on feedback, possibly
    67  # in incompatible ways. We don't recommend building automation around this
    68  # command at this time. If you have feedback about this command, please open
    69  # a feature request issue in the Terraform GitHub repository.
    70  resource "test_instance" "new" {
    71    value = null # REQUIRED string
    72  }
    73  `
    74  
    75  		if !cmp.Equal(output.Stdout(), expected) {
    76  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
    77  		}
    78  	})
    79  
    80  	t.Run("basic to file", func(t *testing.T) {
    81  		view, done := testView(t)
    82  		c := &AddCommand{
    83  			Meta: Meta{
    84  				testingOverrides: overrides,
    85  				View:             view,
    86  			},
    87  		}
    88  		outPath := "add.tf"
    89  		args := []string{fmt.Sprintf("-out=%s", outPath), "test_instance.new"}
    90  		code := c.Run(args)
    91  		output := done(t)
    92  		if code != 0 {
    93  			fmt.Println(output.Stderr())
    94  			t.Fatalf("wrong exit status. Got %d, want 0", code)
    95  		}
    96  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
    97  # starting point for your resource configuration, with some limitations.
    98  #
    99  # The behavior of this command may change in future based on feedback, possibly
   100  # in incompatible ways. We don't recommend building automation around this
   101  # command at this time. If you have feedback about this command, please open
   102  # a feature request issue in the Terraform GitHub repository.
   103  resource "test_instance" "new" {
   104    value = null # REQUIRED string
   105  }
   106  `
   107  		result, err := os.ReadFile(outPath)
   108  		if err != nil {
   109  			t.Fatalf("error reading result file %s: %s", outPath, err.Error())
   110  		}
   111  		// While the entire directory will get removed once the whole test suite
   112  		// is done, we remove this lest it gets in the way of another (not yet
   113  		// written) test.
   114  		os.Remove(outPath)
   115  
   116  		if !cmp.Equal(expected, string(result)) {
   117  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, string(result)))
   118  		}
   119  	})
   120  
   121  	t.Run("basic to existing file", func(t *testing.T) {
   122  		view, done := testView(t)
   123  		c := &AddCommand{
   124  			Meta: Meta{
   125  				testingOverrides: overrides,
   126  				View:             view,
   127  			},
   128  		}
   129  		outPath := "add.tf"
   130  		args := []string{fmt.Sprintf("-out=%s", outPath), "test_instance.new"}
   131  		c.Run(args)
   132  		args = []string{fmt.Sprintf("-out=%s", outPath), "test_instance.new2"}
   133  		code := c.Run(args)
   134  		output := done(t)
   135  		if code != 0 {
   136  			fmt.Println(output.Stderr())
   137  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   138  		}
   139  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   140  # starting point for your resource configuration, with some limitations.
   141  #
   142  # The behavior of this command may change in future based on feedback, possibly
   143  # in incompatible ways. We don't recommend building automation around this
   144  # command at this time. If you have feedback about this command, please open
   145  # a feature request issue in the Terraform GitHub repository.
   146  resource "test_instance" "new" {
   147    value = null # REQUIRED string
   148  }
   149  # NOTE: The "terraform add" command is currently experimental and offers only a
   150  # starting point for your resource configuration, with some limitations.
   151  #
   152  # The behavior of this command may change in future based on feedback, possibly
   153  # in incompatible ways. We don't recommend building automation around this
   154  # command at this time. If you have feedback about this command, please open
   155  # a feature request issue in the Terraform GitHub repository.
   156  resource "test_instance" "new2" {
   157    value = null # REQUIRED string
   158  }
   159  `
   160  		result, err := os.ReadFile(outPath)
   161  		if err != nil {
   162  			t.Fatalf("error reading result file %s: %s", outPath, err.Error())
   163  		}
   164  		// While the entire directory will get removed once the whole test suite
   165  		// is done, we remove this lest it gets in the way of another (not yet
   166  		// written) test.
   167  		os.Remove(outPath)
   168  
   169  		if !cmp.Equal(expected, string(result)) {
   170  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, string(result)))
   171  		}
   172  	})
   173  
   174  	t.Run("optionals", func(t *testing.T) {
   175  		view, done := testView(t)
   176  		c := &AddCommand{
   177  			Meta: Meta{
   178  				testingOverrides: overrides,
   179  				View:             view,
   180  			},
   181  		}
   182  		args := []string{"-optional", "test_instance.new"}
   183  		code := c.Run(args)
   184  		if code != 0 {
   185  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   186  		}
   187  		output := done(t)
   188  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   189  # starting point for your resource configuration, with some limitations.
   190  #
   191  # The behavior of this command may change in future based on feedback, possibly
   192  # in incompatible ways. We don't recommend building automation around this
   193  # command at this time. If you have feedback about this command, please open
   194  # a feature request issue in the Terraform GitHub repository.
   195  resource "test_instance" "new" {
   196    ami   = null # OPTIONAL string
   197    id    = null # OPTIONAL string
   198    value = null # REQUIRED string
   199  }
   200  `
   201  
   202  		if !cmp.Equal(output.Stdout(), expected) {
   203  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   204  		}
   205  	})
   206  
   207  	t.Run("alternate provider for resource", func(t *testing.T) {
   208  		view, done := testView(t)
   209  		c := &AddCommand{
   210  			Meta: Meta{
   211  				testingOverrides: overrides,
   212  				View:             view,
   213  			},
   214  		}
   215  		args := []string{"-provider=provider[\"registry.terraform.io/happycorp/test\"].alias", "test_instance.new"}
   216  		code := c.Run(args)
   217  		output := done(t)
   218  		if code != 0 {
   219  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   220  		}
   221  
   222  		// The provider happycorp/test has a localname "othertest" in the provider configuration.
   223  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   224  # starting point for your resource configuration, with some limitations.
   225  #
   226  # The behavior of this command may change in future based on feedback, possibly
   227  # in incompatible ways. We don't recommend building automation around this
   228  # command at this time. If you have feedback about this command, please open
   229  # a feature request issue in the Terraform GitHub repository.
   230  resource "test_instance" "new" {
   231    provider = othertest.alias
   232  
   233    value = null # REQUIRED string
   234  }
   235  `
   236  
   237  		if !cmp.Equal(output.Stdout(), expected) {
   238  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   239  		}
   240  	})
   241  
   242  	t.Run("resource exists error", func(t *testing.T) {
   243  		view, done := testView(t)
   244  		c := &AddCommand{
   245  			Meta: Meta{
   246  				testingOverrides: overrides,
   247  				View:             view,
   248  			},
   249  		}
   250  		outPath := "add.tf"
   251  		args := []string{fmt.Sprintf("-out=%s", outPath), "test_instance.exists"}
   252  		code := c.Run(args)
   253  		if code != 1 {
   254  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   255  		}
   256  
   257  		output := done(t)
   258  		if !strings.Contains(output.Stderr(), "The resource test_instance.exists is already in this configuration") {
   259  			t.Fatalf("missing expected error message: %s", output.Stderr())
   260  		}
   261  	})
   262  
   263  	t.Run("output existing resource to stdout", func(t *testing.T) {
   264  		view, done := testView(t)
   265  		c := &AddCommand{
   266  			Meta: Meta{
   267  				testingOverrides: overrides,
   268  				View:             view,
   269  			},
   270  		}
   271  		args := []string{"test_instance.exists"}
   272  		code := c.Run(args)
   273  		output := done(t)
   274  		if code != 0 {
   275  			fmt.Println(output.Stderr())
   276  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   277  		}
   278  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   279  # starting point for your resource configuration, with some limitations.
   280  #
   281  # The behavior of this command may change in future based on feedback, possibly
   282  # in incompatible ways. We don't recommend building automation around this
   283  # command at this time. If you have feedback about this command, please open
   284  # a feature request issue in the Terraform GitHub repository.
   285  resource "test_instance" "exists" {
   286    value = null # REQUIRED string
   287  }
   288  `
   289  
   290  		if !cmp.Equal(output.Stdout(), expected) {
   291  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   292  		}
   293  	})
   294  
   295  	t.Run("provider not in configuration", func(t *testing.T) {
   296  		view, done := testView(t)
   297  		c := &AddCommand{
   298  			Meta: Meta{
   299  				testingOverrides: overrides,
   300  				View:             view,
   301  			},
   302  		}
   303  		args := []string{"toast_instance.new"}
   304  		code := c.Run(args)
   305  		if code != 1 {
   306  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   307  		}
   308  
   309  		output := done(t)
   310  		if !strings.Contains(output.Stderr(), "No schema found for provider registry.terraform.io/hashicorp/toast.") {
   311  			t.Fatalf("missing expected error message: %s", output.Stderr())
   312  		}
   313  	})
   314  
   315  	t.Run("no schema for resource", func(t *testing.T) {
   316  		view, done := testView(t)
   317  		c := &AddCommand{
   318  			Meta: Meta{
   319  				testingOverrides: overrides,
   320  				View:             view,
   321  			},
   322  		}
   323  		args := []string{"test_pet.meow"}
   324  		code := c.Run(args)
   325  		if code != 1 {
   326  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   327  		}
   328  
   329  		output := done(t)
   330  		if !strings.Contains(output.Stderr(), "No resource schema found for test_pet.") {
   331  			t.Fatalf("missing expected error message: %s", output.Stderr())
   332  		}
   333  	})
   334  }
   335  
   336  func TestAdd(t *testing.T) {
   337  	td := tempDir(t)
   338  	testCopyDir(t, testFixturePath("add/module"), td)
   339  	defer os.RemoveAll(td)
   340  	defer testChdir(t, td)()
   341  
   342  	// a simple hashicorp/test provider, and a more complex happycorp/test provider
   343  	p := testProvider()
   344  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   345  		ResourceTypes: map[string]providers.Schema{
   346  			"test_instance": {
   347  				Block: &configschema.Block{
   348  					Attributes: map[string]*configschema.Attribute{
   349  						"id": {Type: cty.String, Required: true},
   350  					},
   351  				},
   352  			},
   353  		},
   354  	}
   355  
   356  	happycorp := testProvider()
   357  	happycorp.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   358  		ResourceTypes: map[string]providers.Schema{
   359  			"test_instance": {
   360  				Block: &configschema.Block{
   361  					Attributes: map[string]*configschema.Attribute{
   362  						"id":    {Type: cty.String, Optional: true, Computed: true},
   363  						"ami":   {Type: cty.String, Optional: true, Description: "the ami to use"},
   364  						"value": {Type: cty.String, Required: true, Description: "a value of a thing"},
   365  						"disks": {
   366  							NestedType: &configschema.Object{
   367  								Nesting: configschema.NestingList,
   368  								Attributes: map[string]*configschema.Attribute{
   369  									"size":        {Type: cty.String, Optional: true},
   370  									"mount_point": {Type: cty.String, Required: true},
   371  								},
   372  							},
   373  							Optional: true,
   374  						},
   375  					},
   376  					BlockTypes: map[string]*configschema.NestedBlock{
   377  						"network_interface": {
   378  							Nesting:  configschema.NestingList,
   379  							MinItems: 1,
   380  							Block: configschema.Block{
   381  								Attributes: map[string]*configschema.Attribute{
   382  									"device_index": {Type: cty.String, Optional: true},
   383  									"description":  {Type: cty.String, Optional: true},
   384  								},
   385  							},
   386  						},
   387  					},
   388  				},
   389  			},
   390  		},
   391  	}
   392  	providerSource, psClose := newMockProviderSource(t, map[string][]string{
   393  		"registry.terraform.io/happycorp/test": {"1.0.0"},
   394  		"registry.terraform.io/hashicorp/test": {"1.0.0"},
   395  	})
   396  	defer psClose()
   397  
   398  	overrides := &testingOverrides{
   399  		Providers: map[addrs.Provider]providers.Factory{
   400  			addrs.NewProvider("registry.terraform.io", "happycorp", "test"): providers.FactoryFixed(happycorp),
   401  			addrs.NewDefaultProvider("test"):                                providers.FactoryFixed(p),
   402  		},
   403  	}
   404  
   405  	// the test fixture uses a module, so we need to run init.
   406  	m := Meta{
   407  		testingOverrides: overrides,
   408  		ProviderSource:   providerSource,
   409  		Ui:               new(cli.MockUi),
   410  	}
   411  
   412  	init := &InitCommand{
   413  		Meta: m,
   414  	}
   415  
   416  	code := init.Run([]string{})
   417  	if code != 0 {
   418  		t.Fatal("init failed")
   419  	}
   420  
   421  	t.Run("optional", func(t *testing.T) {
   422  		view, done := testView(t)
   423  		c := &AddCommand{
   424  			Meta: Meta{
   425  				testingOverrides: overrides,
   426  				View:             view,
   427  			},
   428  		}
   429  		args := []string{"-optional", "test_instance.new"}
   430  		code := c.Run(args)
   431  		output := done(t)
   432  		if code != 0 {
   433  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   434  		}
   435  
   436  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   437  # starting point for your resource configuration, with some limitations.
   438  #
   439  # The behavior of this command may change in future based on feedback, possibly
   440  # in incompatible ways. We don't recommend building automation around this
   441  # command at this time. If you have feedback about this command, please open
   442  # a feature request issue in the Terraform GitHub repository.
   443  resource "test_instance" "new" {
   444    ami = null           # OPTIONAL string
   445    disks = [{           # OPTIONAL list of object
   446      mount_point = null # REQUIRED string
   447      size        = null # OPTIONAL string
   448    }]
   449    id    = null          # OPTIONAL string
   450    value = null          # REQUIRED string
   451    network_interface {   # REQUIRED block
   452      description  = null # OPTIONAL string
   453      device_index = null # OPTIONAL string
   454    }
   455  }
   456  `
   457  
   458  		if !cmp.Equal(output.Stdout(), expected) {
   459  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   460  		}
   461  
   462  	})
   463  
   464  	t.Run("chooses correct provider for root module", func(t *testing.T) {
   465  		// in the root module of this test fixture, "test" is the local name for "happycorp/test"
   466  		view, done := testView(t)
   467  		c := &AddCommand{
   468  			Meta: Meta{
   469  				testingOverrides: overrides,
   470  				View:             view,
   471  			},
   472  		}
   473  		args := []string{"test_instance.new"}
   474  		code := c.Run(args)
   475  		output := done(t)
   476  		if code != 0 {
   477  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   478  		}
   479  
   480  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   481  # starting point for your resource configuration, with some limitations.
   482  #
   483  # The behavior of this command may change in future based on feedback, possibly
   484  # in incompatible ways. We don't recommend building automation around this
   485  # command at this time. If you have feedback about this command, please open
   486  # a feature request issue in the Terraform GitHub repository.
   487  resource "test_instance" "new" {
   488    value = null        # REQUIRED string
   489    network_interface { # REQUIRED block
   490    }
   491  }
   492  `
   493  
   494  		if !cmp.Equal(output.Stdout(), expected) {
   495  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   496  		}
   497  	})
   498  
   499  	t.Run("chooses correct provider for child module", func(t *testing.T) {
   500  		// in the child module of this test fixture, "test" is a default "hashicorp/test" provider
   501  		view, done := testView(t)
   502  		c := &AddCommand{
   503  			Meta: Meta{
   504  				testingOverrides: overrides,
   505  				View:             view,
   506  			},
   507  		}
   508  		args := []string{"module.child.test_instance.new"}
   509  		code := c.Run(args)
   510  		output := done(t)
   511  		if code != 0 {
   512  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   513  		}
   514  
   515  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   516  # starting point for your resource configuration, with some limitations.
   517  #
   518  # The behavior of this command may change in future based on feedback, possibly
   519  # in incompatible ways. We don't recommend building automation around this
   520  # command at this time. If you have feedback about this command, please open
   521  # a feature request issue in the Terraform GitHub repository.
   522  resource "test_instance" "new" {
   523    id = null # REQUIRED string
   524  }
   525  `
   526  
   527  		if !cmp.Equal(output.Stdout(), expected) {
   528  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   529  		}
   530  	})
   531  
   532  	t.Run("chooses correct provider for an unknown module", func(t *testing.T) {
   533  		// it's weird but ok to use a new/unknown module name; terraform will
   534  		// fall back on default providers (unless a -provider argument is
   535  		// supplied)
   536  		view, done := testView(t)
   537  		c := &AddCommand{
   538  			Meta: Meta{
   539  				testingOverrides: overrides,
   540  				View:             view,
   541  			},
   542  		}
   543  		args := []string{"module.madeup.test_instance.new"}
   544  		code := c.Run(args)
   545  		output := done(t)
   546  		if code != 0 {
   547  			t.Fatalf("wrong exit status. Got %d, want 0", code)
   548  		}
   549  
   550  		expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   551  # starting point for your resource configuration, with some limitations.
   552  #
   553  # The behavior of this command may change in future based on feedback, possibly
   554  # in incompatible ways. We don't recommend building automation around this
   555  # command at this time. If you have feedback about this command, please open
   556  # a feature request issue in the Terraform GitHub repository.
   557  resource "test_instance" "new" {
   558    id = null # REQUIRED string
   559  }
   560  `
   561  
   562  		if !cmp.Equal(output.Stdout(), expected) {
   563  			t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   564  		}
   565  	})
   566  }
   567  
   568  func TestAdd_from_state(t *testing.T) {
   569  	td := tempDir(t)
   570  	testCopyDir(t, testFixturePath("add/basic"), td)
   571  	defer os.RemoveAll(td)
   572  	defer testChdir(t, td)()
   573  
   574  	// write some state
   575  	testState := states.BuildState(func(s *states.SyncState) {
   576  		s.SetResourceInstanceCurrent(
   577  			addrs.Resource{
   578  				Mode: addrs.ManagedResourceMode,
   579  				Type: "test_instance",
   580  				Name: "new",
   581  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   582  			&states.ResourceInstanceObjectSrc{
   583  				AttrsJSON:    []byte("{\"id\":\"bar\",\"ami\":\"ami-123456\",\"disks\":[{\"mount_point\":\"diska\",\"size\":null}],\"value\":\"bloop\"}"),
   584  				Status:       states.ObjectReady,
   585  				Dependencies: []addrs.ConfigResource{},
   586  			},
   587  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   588  		)
   589  	})
   590  	f, err := os.Create("terraform.tfstate")
   591  	if err != nil {
   592  		t.Fatalf("failed to create temporary state file: %s", err)
   593  	}
   594  	defer f.Close()
   595  	err = writeStateForTesting(testState, f)
   596  	if err != nil {
   597  		t.Fatalf("failed to write state file: %s", err)
   598  	}
   599  
   600  	p := testProvider()
   601  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   602  		ResourceTypes: map[string]providers.Schema{
   603  			"test_instance": {
   604  				Block: &configschema.Block{
   605  					Attributes: map[string]*configschema.Attribute{
   606  						"id":    {Type: cty.String, Optional: true, Computed: true},
   607  						"ami":   {Type: cty.String, Optional: true, Description: "the ami to use"},
   608  						"value": {Type: cty.String, Required: true, Description: "a value of a thing"},
   609  						"disks": {
   610  							NestedType: &configschema.Object{
   611  								Nesting: configschema.NestingList,
   612  								Attributes: map[string]*configschema.Attribute{
   613  									"size":        {Type: cty.String, Optional: true},
   614  									"mount_point": {Type: cty.String, Required: true},
   615  								},
   616  							},
   617  							Optional: true,
   618  						},
   619  					},
   620  					BlockTypes: map[string]*configschema.NestedBlock{
   621  						"network_interface": {
   622  							Nesting:  configschema.NestingList,
   623  							MinItems: 1,
   624  							Block: configschema.Block{
   625  								Attributes: map[string]*configschema.Attribute{
   626  									"device_index": {Type: cty.String, Optional: true},
   627  									"description":  {Type: cty.String, Optional: true},
   628  								},
   629  							},
   630  						},
   631  					},
   632  				},
   633  			},
   634  		},
   635  	}
   636  	overrides := &testingOverrides{
   637  		Providers: map[addrs.Provider]providers.Factory{
   638  			addrs.NewDefaultProvider("test"):                                providers.FactoryFixed(p),
   639  			addrs.NewProvider("registry.terraform.io", "happycorp", "test"): providers.FactoryFixed(p),
   640  		},
   641  	}
   642  	view, done := testView(t)
   643  	c := &AddCommand{
   644  		Meta: Meta{
   645  			testingOverrides: overrides,
   646  			View:             view,
   647  		},
   648  	}
   649  
   650  	args := []string{"-from-state", "test_instance.new"}
   651  	code := c.Run(args)
   652  	output := done(t)
   653  	if code != 0 {
   654  		fmt.Println(output.Stderr())
   655  		t.Fatalf("wrong exit status. Got %d, want 0", code)
   656  	}
   657  
   658  	expected := `# NOTE: The "terraform add" command is currently experimental and offers only a
   659  # starting point for your resource configuration, with some limitations.
   660  #
   661  # The behavior of this command may change in future based on feedback, possibly
   662  # in incompatible ways. We don't recommend building automation around this
   663  # command at this time. If you have feedback about this command, please open
   664  # a feature request issue in the Terraform GitHub repository.
   665  resource "test_instance" "new" {
   666    ami = "ami-123456"
   667    disks = [
   668      {
   669        mount_point = "diska"
   670        size        = null
   671      },
   672    ]
   673    id    = "bar"
   674    value = "bloop"
   675  }
   676  `
   677  
   678  	if !cmp.Equal(output.Stdout(), expected) {
   679  		t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
   680  	}
   681  
   682  	if _, err := os.Stat(filepath.Join(td, ".terraform.tfstate.lock.info")); !os.IsNotExist(err) {
   683  		t.Fatal("state left locked after add")
   684  	}
   685  }