44package command
55
66import (
7+ "bytes"
8+ "encoding/json"
79 "fmt"
810 "os"
911 "path/filepath"
@@ -14,7 +16,9 @@ import (
1416 "github.com/hashicorp/cli"
1517
1618 "github.com/hashicorp/terraform/internal/addrs"
19+ "github.com/hashicorp/terraform/internal/providers"
1720 "github.com/hashicorp/terraform/internal/states"
21+ "github.com/hashicorp/terraform/internal/states/statefile"
1822)
1923
2024func TestStateMv (t * testing.T ) {
@@ -153,6 +157,137 @@ func TestStateMv(t *testing.T) {
153157
154158}
155159
160+ func TestStateMv_stateStore (t * testing.T ) {
161+ // Create a temporary working directory
162+ td := t .TempDir ()
163+ testCopyDir (t , testFixturePath ("state-list-state-store" ), td )
164+ t .Chdir (td )
165+
166+ // Get bytes describing a state containing resources
167+ state := states .BuildState (func (s * states.SyncState ) {
168+ s .SetResourceInstanceCurrent (
169+ addrs.Resource {
170+ Mode : addrs .ManagedResourceMode ,
171+ Type : "test_instance" ,
172+ Name : "foo" ,
173+ }.Instance (addrs .NoKey ).Absolute (addrs .RootModuleInstance ),
174+ & states.ResourceInstanceObjectSrc {
175+ AttrsJSON : []byte (`{"id":"foo","foo":"value","bar":"value"}` ),
176+ Status : states .ObjectReady ,
177+ },
178+ addrs.AbsProviderConfig {
179+ Provider : addrs .NewDefaultProvider ("test" ),
180+ Module : addrs .RootModule ,
181+ },
182+ )
183+ s .SetResourceInstanceCurrent (
184+ addrs.Resource {
185+ Mode : addrs .ManagedResourceMode ,
186+ Type : "test_instance" ,
187+ Name : "baz" ,
188+ }.Instance (addrs .NoKey ).Absolute (addrs .RootModuleInstance ),
189+ & states.ResourceInstanceObjectSrc {
190+ AttrsJSON : []byte (`{"id":"baz","foo":"value","bar":"value"}` ),
191+ Status : states .ObjectReady ,
192+ },
193+ addrs.AbsProviderConfig {
194+ Provider : addrs .NewDefaultProvider ("test" ),
195+ Module : addrs .RootModule ,
196+ },
197+ )
198+ })
199+ var stateBuf bytes.Buffer
200+ if err := statefile .Write (statefile .New (state , "" , 1 ), & stateBuf ); err != nil {
201+ t .Fatalf ("error during test setup: %s" , err )
202+ }
203+
204+ // Create a mock that contains a persisted "default" state that uses the bytes from above.
205+ mockProvider := mockPluggableStateStorageProvider (t )
206+ mockProvider .MockStates = map [string ]interface {}{
207+ "default" : stateBuf .Bytes (),
208+ }
209+ mockProviderAddress := addrs .NewDefaultProvider ("test" )
210+ providerSource , close := newMockProviderSource (t , map [string ][]string {
211+ "hashicorp/test" : {"1.0.0" },
212+ })
213+ defer close ()
214+
215+ // Make the mock assert that the resource has been moved when the new state is persisted
216+ oldAddr := "test_instance.foo"
217+ newAddr := "test_instance.bar"
218+ mockProvider .WriteStateBytesFn = func (req providers.WriteStateBytesRequest ) providers.WriteStateBytesResponse {
219+ r := bytes .NewReader (req .Bytes )
220+ file , err := statefile .Read (r )
221+ if err != nil {
222+ t .Fatal (err )
223+ }
224+
225+ root := file .State .Modules ["" ]
226+ if _ , ok := root .Resources [oldAddr ]; ok {
227+ t .Fatalf ("expected the new state to have moved the %s resource to the new addr %s, but the old addr is still present" ,
228+ newAddr ,
229+ oldAddr ,
230+ )
231+ }
232+ resource , ok := root .Resources [newAddr ]
233+ if ! ok {
234+ t .Fatalf ("expected the moved resource to be at addr %s, but it isn't present" , newAddr )
235+ }
236+
237+ // Check that the moved resource has the same state.
238+ var key addrs.InstanceKey
239+ type attrsJson struct {
240+ Id string `json:"id"`
241+ Foo string `json:"foo"`
242+ Bar string `json:"bar"`
243+ }
244+ var data attrsJson
245+ attrs := resource .Instances [key ].Current .AttrsJSON
246+ err = json .Unmarshal (attrs , & data )
247+ if err != nil {
248+ t .Fatal (err )
249+ }
250+ expectedData := attrsJson {
251+ Id : "foo" ,
252+ Foo : "value" ,
253+ Bar : "value" ,
254+ }
255+ if diff := cmp .Diff (expectedData , data ); diff != "" {
256+ t .Fatalf ("the state of the moved resource doesn't match the original state:\n Diff = %s" , diff )
257+ }
258+
259+ return providers.WriteStateBytesResponse {}
260+ }
261+
262+ ui := new (cli.MockUi )
263+ view , _ := testView (t )
264+ c := & StateMvCommand {
265+ StateMeta {
266+ Meta : Meta {
267+ AllowExperimentalFeatures : true ,
268+ testingOverrides : & testingOverrides {
269+ Providers : map [addrs.Provider ]providers.Factory {
270+ mockProviderAddress : providers .FactoryFixed (mockProvider ),
271+ },
272+ },
273+ ProviderSource : providerSource ,
274+ Ui : ui ,
275+ View : view ,
276+ },
277+ },
278+ }
279+
280+ args := []string {
281+ oldAddr ,
282+ newAddr ,
283+ }
284+ if code := c .Run (args ); code != 0 {
285+ t .Fatalf ("return code: %d\n \n %s" , code , ui .ErrorWriter .String ())
286+ }
287+
288+ // See the mock definition above for logic that asserts what the new state will look like after moving the resource.
289+ }
290+
156291func TestStateMv_backupAndBackupOutOptionsWithNonLocalBackend (t * testing.T ) {
157292 state := states .BuildState (func (s * states.SyncState ) {
158293 s .SetResourceInstanceCurrent (
0 commit comments