github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/apply_destroy_test.go (about)

     1  package command
     2  
     3  import (
     4  	"os"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/davecgh/go-spew/spew"
     9  	"github.com/mitchellh/cli"
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/hashicorp/terraform/addrs"
    13  	"github.com/hashicorp/terraform/configs/configschema"
    14  	"github.com/hashicorp/terraform/providers"
    15  	"github.com/hashicorp/terraform/states"
    16  	"github.com/hashicorp/terraform/states/statefile"
    17  	"github.com/hashicorp/terraform/terraform"
    18  )
    19  
    20  func TestApply_destroy(t *testing.T) {
    21  	originalState := states.BuildState(func(s *states.SyncState) {
    22  		s.SetResourceInstanceCurrent(
    23  			addrs.Resource{
    24  				Mode: addrs.ManagedResourceMode,
    25  				Type: "test_instance",
    26  				Name: "foo",
    27  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    28  			&states.ResourceInstanceObjectSrc{
    29  				AttrsJSON: []byte(`{"id":"bar"}`),
    30  				Status:    states.ObjectReady,
    31  			},
    32  			addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
    33  		)
    34  	})
    35  	statePath := testStateFile(t, originalState)
    36  
    37  	p := testProvider()
    38  	p.GetSchemaReturn = &terraform.ProviderSchema{
    39  		ResourceTypes: map[string]*configschema.Block{
    40  			"test_instance": {
    41  				Attributes: map[string]*configschema.Attribute{
    42  					"id":  {Type: cty.String, Computed: true},
    43  					"ami": {Type: cty.String, Optional: true},
    44  				},
    45  			},
    46  		},
    47  	}
    48  
    49  	ui := new(cli.MockUi)
    50  	c := &ApplyCommand{
    51  		Destroy: true,
    52  		Meta: Meta{
    53  			testingOverrides: metaOverridesForProvider(p),
    54  			Ui:               ui,
    55  		},
    56  	}
    57  
    58  	// Run the apply command pointing to our existing state
    59  	args := []string{
    60  		"-auto-approve",
    61  		"-state", statePath,
    62  		testFixturePath("apply"),
    63  	}
    64  	if code := c.Run(args); code != 0 {
    65  		t.Log(ui.OutputWriter.String())
    66  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    67  	}
    68  
    69  	// Verify a new state exists
    70  	if _, err := os.Stat(statePath); err != nil {
    71  		t.Fatalf("err: %s", err)
    72  	}
    73  
    74  	f, err := os.Open(statePath)
    75  	if err != nil {
    76  		t.Fatalf("err: %s", err)
    77  	}
    78  	defer f.Close()
    79  
    80  	stateFile, err := statefile.Read(f)
    81  	if err != nil {
    82  		t.Fatalf("err: %s", err)
    83  	}
    84  	if stateFile.State == nil {
    85  		t.Fatal("state should not be nil")
    86  	}
    87  
    88  	actualStr := strings.TrimSpace(stateFile.State.String())
    89  	expectedStr := strings.TrimSpace(testApplyDestroyStr)
    90  	if actualStr != expectedStr {
    91  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
    92  	}
    93  
    94  	// Should have a backup file
    95  	f, err = os.Open(statePath + DefaultBackupExtension)
    96  	if err != nil {
    97  		t.Fatalf("err: %s", err)
    98  	}
    99  
   100  	backupStateFile, err := statefile.Read(f)
   101  	f.Close()
   102  	if err != nil {
   103  		t.Fatalf("err: %s", err)
   104  	}
   105  
   106  	actualStr = strings.TrimSpace(backupStateFile.State.String())
   107  	expectedStr = strings.TrimSpace(originalState.String())
   108  	if actualStr != expectedStr {
   109  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   110  	}
   111  }
   112  
   113  func TestApply_destroyLockedState(t *testing.T) {
   114  	originalState := states.BuildState(func(s *states.SyncState) {
   115  		s.SetResourceInstanceCurrent(
   116  			addrs.Resource{
   117  				Mode: addrs.ManagedResourceMode,
   118  				Type: "test_instance",
   119  				Name: "foo",
   120  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   121  			&states.ResourceInstanceObjectSrc{
   122  				AttrsJSON: []byte(`{"id":"bar"}`),
   123  				Status:    states.ObjectReady,
   124  			},
   125  			addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
   126  		)
   127  	})
   128  	statePath := testStateFile(t, originalState)
   129  
   130  	unlock, err := testLockState(testDataDir, statePath)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	defer unlock()
   135  
   136  	p := testProvider()
   137  	ui := new(cli.MockUi)
   138  	c := &ApplyCommand{
   139  		Destroy: true,
   140  		Meta: Meta{
   141  			testingOverrides: metaOverridesForProvider(p),
   142  			Ui:               ui,
   143  		},
   144  	}
   145  
   146  	// Run the apply command pointing to our existing state
   147  	args := []string{
   148  		"-auto-approve",
   149  		"-state", statePath,
   150  		testFixturePath("apply"),
   151  	}
   152  
   153  	if code := c.Run(args); code == 0 {
   154  		t.Fatal("expected error")
   155  	}
   156  
   157  	output := ui.ErrorWriter.String()
   158  	if !strings.Contains(output, "lock") {
   159  		t.Fatal("command output does not look like a lock error:", output)
   160  	}
   161  }
   162  
   163  func TestApply_destroyPlan(t *testing.T) {
   164  	planPath := testPlanFileNoop(t)
   165  
   166  	p := testProvider()
   167  	ui := new(cli.MockUi)
   168  	c := &ApplyCommand{
   169  		Destroy: true,
   170  		Meta: Meta{
   171  			testingOverrides: metaOverridesForProvider(p),
   172  			Ui:               ui,
   173  		},
   174  	}
   175  
   176  	// Run the apply command pointing to our existing state
   177  	args := []string{
   178  		planPath,
   179  	}
   180  	if code := c.Run(args); code != 1 {
   181  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   182  	}
   183  }
   184  
   185  func TestApply_destroyTargeted(t *testing.T) {
   186  	originalState := states.BuildState(func(s *states.SyncState) {
   187  		s.SetResourceInstanceCurrent(
   188  			addrs.Resource{
   189  				Mode: addrs.ManagedResourceMode,
   190  				Type: "test_instance",
   191  				Name: "foo",
   192  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   193  			&states.ResourceInstanceObjectSrc{
   194  				AttrsJSON: []byte(`{"id":"i-ab123"}`),
   195  				Status:    states.ObjectReady,
   196  			},
   197  			addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
   198  		)
   199  		s.SetResourceInstanceCurrent(
   200  			addrs.Resource{
   201  				Mode: addrs.ManagedResourceMode,
   202  				Type: "test_load_balancer",
   203  				Name: "foo",
   204  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   205  			&states.ResourceInstanceObjectSrc{
   206  				AttrsJSON: []byte(`{"id":"i-abc123"}`),
   207  				Status:    states.ObjectReady,
   208  			},
   209  			addrs.ProviderConfig{Type: addrs.NewLegacyProvider("test")}.Absolute(addrs.RootModuleInstance),
   210  		)
   211  	})
   212  	statePath := testStateFile(t, originalState)
   213  
   214  	p := testProvider()
   215  	p.GetSchemaReturn = &terraform.ProviderSchema{
   216  		ResourceTypes: map[string]*configschema.Block{
   217  			"test_instance": {
   218  				Attributes: map[string]*configschema.Attribute{
   219  					"id": {Type: cty.String, Computed: true},
   220  				},
   221  			},
   222  			"test_load_balancer": {
   223  				Attributes: map[string]*configschema.Attribute{
   224  					"id":        {Type: cty.String, Computed: true},
   225  					"instances": {Type: cty.List(cty.String), Optional: true},
   226  				},
   227  			},
   228  		},
   229  	}
   230  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   231  		return providers.PlanResourceChangeResponse{
   232  			PlannedState: req.ProposedNewState,
   233  		}
   234  	}
   235  
   236  	ui := new(cli.MockUi)
   237  	c := &ApplyCommand{
   238  		Destroy: true,
   239  		Meta: Meta{
   240  			testingOverrides: metaOverridesForProvider(p),
   241  			Ui:               ui,
   242  		},
   243  	}
   244  
   245  	// Run the apply command pointing to our existing state
   246  	args := []string{
   247  		"-auto-approve",
   248  		"-target", "test_instance.foo",
   249  		"-state", statePath,
   250  		testFixturePath("apply-destroy-targeted"),
   251  	}
   252  	if code := c.Run(args); code != 0 {
   253  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   254  	}
   255  
   256  	// Verify a new state exists
   257  	if _, err := os.Stat(statePath); err != nil {
   258  		t.Fatalf("err: %s", err)
   259  	}
   260  
   261  	f, err := os.Open(statePath)
   262  	if err != nil {
   263  		t.Fatalf("err: %s", err)
   264  	}
   265  	defer f.Close()
   266  
   267  	stateFile, err := statefile.Read(f)
   268  	if err != nil {
   269  		t.Fatalf("err: %s", err)
   270  	}
   271  	if stateFile == nil || stateFile.State == nil {
   272  		t.Fatal("state should not be nil")
   273  	}
   274  
   275  	spew.Config.DisableMethods = true
   276  	if !stateFile.State.Empty() {
   277  		t.Fatalf("unexpected final state\ngot: %s\nwant: empty state", spew.Sdump(stateFile.State))
   278  	}
   279  
   280  	// Should have a backup file
   281  	f, err = os.Open(statePath + DefaultBackupExtension)
   282  	if err != nil {
   283  		t.Fatalf("err: %s", err)
   284  	}
   285  
   286  	backupStateFile, err := statefile.Read(f)
   287  	f.Close()
   288  	if err != nil {
   289  		t.Fatalf("err: %s", err)
   290  	}
   291  
   292  	actualStr := strings.TrimSpace(backupStateFile.State.String())
   293  	expectedStr := strings.TrimSpace(originalState.String())
   294  	if actualStr != expectedStr {
   295  		t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr)
   296  	}
   297  }
   298  
   299  const testApplyDestroyStr = `
   300  <no state>
   301  `