github.com/pulumi/terraform@v1.4.0/pkg/command/state_replace_provider_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/mitchellh/cli"
    10  
    11  	"github.com/pulumi/terraform/pkg/addrs"
    12  	"github.com/pulumi/terraform/pkg/states"
    13  )
    14  
    15  func TestStateReplaceProvider(t *testing.T) {
    16  	state := states.BuildState(func(s *states.SyncState) {
    17  		s.SetResourceInstanceCurrent(
    18  			addrs.Resource{
    19  				Mode: addrs.ManagedResourceMode,
    20  				Type: "aws_instance",
    21  				Name: "alpha",
    22  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    23  			&states.ResourceInstanceObjectSrc{
    24  				AttrsJSON: []byte(`{"id":"alpha","foo":"value","bar":"value"}`),
    25  				Status:    states.ObjectReady,
    26  			},
    27  			addrs.AbsProviderConfig{
    28  				Provider: addrs.NewDefaultProvider("aws"),
    29  				Module:   addrs.RootModule,
    30  			},
    31  		)
    32  		s.SetResourceInstanceCurrent(
    33  			addrs.Resource{
    34  				Mode: addrs.ManagedResourceMode,
    35  				Type: "aws_instance",
    36  				Name: "beta",
    37  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    38  			&states.ResourceInstanceObjectSrc{
    39  				AttrsJSON: []byte(`{"id":"beta","foo":"value","bar":"value"}`),
    40  				Status:    states.ObjectReady,
    41  			},
    42  			addrs.AbsProviderConfig{
    43  				Provider: addrs.NewDefaultProvider("aws"),
    44  				Module:   addrs.RootModule,
    45  			},
    46  		)
    47  		s.SetResourceInstanceCurrent(
    48  			addrs.Resource{
    49  				Mode: addrs.ManagedResourceMode,
    50  				Type: "azurerm_virtual_machine",
    51  				Name: "gamma",
    52  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    53  			&states.ResourceInstanceObjectSrc{
    54  				AttrsJSON: []byte(`{"id":"gamma","baz":"value"}`),
    55  				Status:    states.ObjectReady,
    56  			},
    57  			addrs.AbsProviderConfig{
    58  				Provider: addrs.NewLegacyProvider("azurerm"),
    59  				Module:   addrs.RootModule,
    60  			},
    61  		)
    62  	})
    63  
    64  	t.Run("happy path", func(t *testing.T) {
    65  		statePath := testStateFile(t, state)
    66  
    67  		ui := new(cli.MockUi)
    68  		view, _ := testView(t)
    69  		c := &StateReplaceProviderCommand{
    70  			StateMeta{
    71  				Meta: Meta{
    72  					Ui:   ui,
    73  					View: view,
    74  				},
    75  			},
    76  		}
    77  
    78  		inputBuf := &bytes.Buffer{}
    79  		ui.InputReader = inputBuf
    80  		inputBuf.WriteString("yes\n")
    81  
    82  		args := []string{
    83  			"-state", statePath,
    84  			"hashicorp/aws",
    85  			"acmecorp/aws",
    86  		}
    87  		if code := c.Run(args); code != 0 {
    88  			t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String())
    89  		}
    90  
    91  		testStateOutput(t, statePath, testStateReplaceProviderOutput)
    92  
    93  		backups := testStateBackups(t, filepath.Dir(statePath))
    94  		if len(backups) != 1 {
    95  			t.Fatalf("unexpected backups: %#v", backups)
    96  		}
    97  		testStateOutput(t, backups[0], testStateReplaceProviderOutputOriginal)
    98  	})
    99  
   100  	t.Run("auto approve", func(t *testing.T) {
   101  		statePath := testStateFile(t, state)
   102  
   103  		ui := new(cli.MockUi)
   104  		view, _ := testView(t)
   105  		c := &StateReplaceProviderCommand{
   106  			StateMeta{
   107  				Meta: Meta{
   108  					Ui:   ui,
   109  					View: view,
   110  				},
   111  			},
   112  		}
   113  
   114  		inputBuf := &bytes.Buffer{}
   115  		ui.InputReader = inputBuf
   116  
   117  		args := []string{
   118  			"-state", statePath,
   119  			"-auto-approve",
   120  			"hashicorp/aws",
   121  			"acmecorp/aws",
   122  		}
   123  		if code := c.Run(args); code != 0 {
   124  			t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String())
   125  		}
   126  
   127  		testStateOutput(t, statePath, testStateReplaceProviderOutput)
   128  
   129  		backups := testStateBackups(t, filepath.Dir(statePath))
   130  		if len(backups) != 1 {
   131  			t.Fatalf("unexpected backups: %#v", backups)
   132  		}
   133  		testStateOutput(t, backups[0], testStateReplaceProviderOutputOriginal)
   134  	})
   135  
   136  	t.Run("cancel at approval step", func(t *testing.T) {
   137  		statePath := testStateFile(t, state)
   138  
   139  		ui := new(cli.MockUi)
   140  		view, _ := testView(t)
   141  		c := &StateReplaceProviderCommand{
   142  			StateMeta{
   143  				Meta: Meta{
   144  					Ui:   ui,
   145  					View: view,
   146  				},
   147  			},
   148  		}
   149  
   150  		inputBuf := &bytes.Buffer{}
   151  		ui.InputReader = inputBuf
   152  		inputBuf.WriteString("no\n")
   153  
   154  		args := []string{
   155  			"-state", statePath,
   156  			"hashicorp/aws",
   157  			"acmecorp/aws",
   158  		}
   159  		if code := c.Run(args); code != 0 {
   160  			t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String())
   161  		}
   162  
   163  		testStateOutput(t, statePath, testStateReplaceProviderOutputOriginal)
   164  
   165  		backups := testStateBackups(t, filepath.Dir(statePath))
   166  		if len(backups) != 0 {
   167  			t.Fatalf("unexpected backups: %#v", backups)
   168  		}
   169  	})
   170  
   171  	t.Run("no matching provider found", func(t *testing.T) {
   172  		statePath := testStateFile(t, state)
   173  
   174  		ui := new(cli.MockUi)
   175  		view, _ := testView(t)
   176  		c := &StateReplaceProviderCommand{
   177  			StateMeta{
   178  				Meta: Meta{
   179  					Ui:   ui,
   180  					View: view,
   181  				},
   182  			},
   183  		}
   184  
   185  		args := []string{
   186  			"-state", statePath,
   187  			"hashicorp/google",
   188  			"acmecorp/google",
   189  		}
   190  		if code := c.Run(args); code != 0 {
   191  			t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String())
   192  		}
   193  
   194  		testStateOutput(t, statePath, testStateReplaceProviderOutputOriginal)
   195  
   196  		backups := testStateBackups(t, filepath.Dir(statePath))
   197  		if len(backups) != 0 {
   198  			t.Fatalf("unexpected backups: %#v", backups)
   199  		}
   200  	})
   201  
   202  	t.Run("invalid flags", func(t *testing.T) {
   203  		ui := new(cli.MockUi)
   204  		view, _ := testView(t)
   205  		c := &StateReplaceProviderCommand{
   206  			StateMeta{
   207  				Meta: Meta{
   208  					Ui:   ui,
   209  					View: view,
   210  				},
   211  			},
   212  		}
   213  
   214  		args := []string{
   215  			"-invalid",
   216  			"hashicorp/google",
   217  			"acmecorp/google",
   218  		}
   219  		if code := c.Run(args); code == 0 {
   220  			t.Fatalf("successful exit; want error")
   221  		}
   222  
   223  		if got, want := ui.ErrorWriter.String(), "Error parsing command-line flags"; !strings.Contains(got, want) {
   224  			t.Fatalf("missing expected error message\nwant: %s\nfull output:\n%s", want, got)
   225  		}
   226  	})
   227  
   228  	t.Run("wrong number of arguments", func(t *testing.T) {
   229  		ui := new(cli.MockUi)
   230  		view, _ := testView(t)
   231  		c := &StateReplaceProviderCommand{
   232  			StateMeta{
   233  				Meta: Meta{
   234  					Ui:   ui,
   235  					View: view,
   236  				},
   237  			},
   238  		}
   239  
   240  		args := []string{"a", "b", "c", "d"}
   241  		if code := c.Run(args); code == 0 {
   242  			t.Fatalf("successful exit; want error")
   243  		}
   244  
   245  		if got, want := ui.ErrorWriter.String(), "Exactly two arguments expected"; !strings.Contains(got, want) {
   246  			t.Fatalf("missing expected error message\nwant: %s\nfull output:\n%s", want, got)
   247  		}
   248  	})
   249  
   250  	t.Run("invalid provider strings", func(t *testing.T) {
   251  		ui := new(cli.MockUi)
   252  		view, _ := testView(t)
   253  		c := &StateReplaceProviderCommand{
   254  			StateMeta{
   255  				Meta: Meta{
   256  					Ui:   ui,
   257  					View: view,
   258  				},
   259  			},
   260  		}
   261  
   262  		args := []string{
   263  			"hashicorp/google_cloud",
   264  			"-/-/google",
   265  		}
   266  		if code := c.Run(args); code == 0 {
   267  			t.Fatalf("successful exit; want error")
   268  		}
   269  
   270  		got := ui.ErrorWriter.String()
   271  		msgs := []string{
   272  			`Invalid "from" provider "hashicorp/google_cloud"`,
   273  			"Invalid provider type",
   274  			`Invalid "to" provider "-/-/google"`,
   275  			"Invalid provider source hostname",
   276  		}
   277  		for _, msg := range msgs {
   278  			if !strings.Contains(got, msg) {
   279  				t.Errorf("missing expected error message\nwant: %s\nfull output:\n%s", msg, got)
   280  			}
   281  		}
   282  	})
   283  }
   284  
   285  func TestStateReplaceProvider_docs(t *testing.T) {
   286  	c := &StateReplaceProviderCommand{}
   287  
   288  	if got, want := c.Help(), "Usage: terraform [global options] state replace-provider"; !strings.Contains(got, want) {
   289  		t.Fatalf("unexpected help text\nwant: %s\nfull output:\n%s", want, got)
   290  	}
   291  
   292  	if got, want := c.Synopsis(), "Replace provider in the state"; got != want {
   293  		t.Fatalf("unexpected synopsis\nwant: %s\nfull output:\n%s", want, got)
   294  	}
   295  }
   296  
   297  func TestStateReplaceProvider_checkRequiredVersion(t *testing.T) {
   298  	// Create a temporary working directory that is empty
   299  	td := t.TempDir()
   300  	testCopyDir(t, testFixturePath("command-check-required-version"), td)
   301  	defer testChdir(t, td)()
   302  
   303  	state := states.BuildState(func(s *states.SyncState) {
   304  		s.SetResourceInstanceCurrent(
   305  			addrs.Resource{
   306  				Mode: addrs.ManagedResourceMode,
   307  				Type: "aws_instance",
   308  				Name: "alpha",
   309  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   310  			&states.ResourceInstanceObjectSrc{
   311  				AttrsJSON: []byte(`{"id":"alpha","foo":"value","bar":"value"}`),
   312  				Status:    states.ObjectReady,
   313  			},
   314  			addrs.AbsProviderConfig{
   315  				Provider: addrs.NewDefaultProvider("aws"),
   316  				Module:   addrs.RootModule,
   317  			},
   318  		)
   319  		s.SetResourceInstanceCurrent(
   320  			addrs.Resource{
   321  				Mode: addrs.ManagedResourceMode,
   322  				Type: "aws_instance",
   323  				Name: "beta",
   324  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   325  			&states.ResourceInstanceObjectSrc{
   326  				AttrsJSON: []byte(`{"id":"beta","foo":"value","bar":"value"}`),
   327  				Status:    states.ObjectReady,
   328  			},
   329  			addrs.AbsProviderConfig{
   330  				Provider: addrs.NewDefaultProvider("aws"),
   331  				Module:   addrs.RootModule,
   332  			},
   333  		)
   334  		s.SetResourceInstanceCurrent(
   335  			addrs.Resource{
   336  				Mode: addrs.ManagedResourceMode,
   337  				Type: "azurerm_virtual_machine",
   338  				Name: "gamma",
   339  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   340  			&states.ResourceInstanceObjectSrc{
   341  				AttrsJSON: []byte(`{"id":"gamma","baz":"value"}`),
   342  				Status:    states.ObjectReady,
   343  			},
   344  			addrs.AbsProviderConfig{
   345  				Provider: addrs.NewLegacyProvider("azurerm"),
   346  				Module:   addrs.RootModule,
   347  			},
   348  		)
   349  	})
   350  
   351  	statePath := testStateFile(t, state)
   352  
   353  	ui := new(cli.MockUi)
   354  	view, _ := testView(t)
   355  	c := &StateReplaceProviderCommand{
   356  		StateMeta{
   357  			Meta: Meta{
   358  				Ui:   ui,
   359  				View: view,
   360  			},
   361  		},
   362  	}
   363  
   364  	inputBuf := &bytes.Buffer{}
   365  	ui.InputReader = inputBuf
   366  	inputBuf.WriteString("yes\n")
   367  
   368  	args := []string{
   369  		"-state", statePath,
   370  		"hashicorp/aws",
   371  		"acmecorp/aws",
   372  	}
   373  	if code := c.Run(args); code != 1 {
   374  		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
   375  	}
   376  
   377  	// State is unchanged
   378  	testStateOutput(t, statePath, testStateReplaceProviderOutputOriginal)
   379  
   380  	// Required version diags are correct
   381  	errStr := ui.ErrorWriter.String()
   382  	if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
   383  		t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
   384  	}
   385  	if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
   386  		t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
   387  	}
   388  }
   389  
   390  const testStateReplaceProviderOutputOriginal = `
   391  aws_instance.alpha:
   392    ID = alpha
   393    provider = provider["registry.terraform.io/hashicorp/aws"]
   394    bar = value
   395    foo = value
   396  aws_instance.beta:
   397    ID = beta
   398    provider = provider["registry.terraform.io/hashicorp/aws"]
   399    bar = value
   400    foo = value
   401  azurerm_virtual_machine.gamma:
   402    ID = gamma
   403    provider = provider["registry.terraform.io/-/azurerm"]
   404    baz = value
   405  `
   406  
   407  const testStateReplaceProviderOutput = `
   408  aws_instance.alpha:
   409    ID = alpha
   410    provider = provider["registry.terraform.io/acmecorp/aws"]
   411    bar = value
   412    foo = value
   413  aws_instance.beta:
   414    ID = beta
   415    provider = provider["registry.terraform.io/acmecorp/aws"]
   416    bar = value
   417    foo = value
   418  azurerm_virtual_machine.gamma:
   419    ID = gamma
   420    provider = provider["registry.terraform.io/-/azurerm"]
   421    baz = value
   422  `