github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/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/iaas-resource-provision/iaas-rpc/internal/addrs"
    12  	"github.com/iaas-resource-provision/iaas-rpc/internal/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  const testStateReplaceProviderOutputOriginal = `
   298  aws_instance.alpha:
   299    ID = alpha
   300    provider = provider["registry.terraform.io/hashicorp/aws"]
   301    bar = value
   302    foo = value
   303  aws_instance.beta:
   304    ID = beta
   305    provider = provider["registry.terraform.io/hashicorp/aws"]
   306    bar = value
   307    foo = value
   308  azurerm_virtual_machine.gamma:
   309    ID = gamma
   310    provider = provider["registry.terraform.io/-/azurerm"]
   311    baz = value
   312  `
   313  
   314  const testStateReplaceProviderOutput = `
   315  aws_instance.alpha:
   316    ID = alpha
   317    provider = provider["registry.terraform.io/acmecorp/aws"]
   318    bar = value
   319    foo = value
   320  aws_instance.beta:
   321    ID = beta
   322    provider = provider["registry.terraform.io/acmecorp/aws"]
   323    bar = value
   324    foo = value
   325  azurerm_virtual_machine.gamma:
   326    ID = gamma
   327    provider = provider["registry.terraform.io/-/azurerm"]
   328    baz = value
   329  `