@@ -193,6 +193,121 @@ Plan: 1 to add, 0 to change, 1 to destroy.`
193193 }
194194}
195195
196+ func TestLocal_planDeposedOnly (t * testing.T ) {
197+ b , cleanup := TestLocal (t )
198+ defer cleanup ()
199+ p := TestLocalProvider (t , b , "test" , planFixtureSchema ())
200+ testStateFile (t , b .StatePath , states .BuildState (func (ss * states.SyncState ) {
201+ ss .SetResourceInstanceDeposed (
202+ addrs.Resource {
203+ Mode : addrs .ManagedResourceMode ,
204+ Type : "test_instance" ,
205+ Name : "foo" ,
206+ }.Instance (addrs .NoKey ).Absolute (addrs .RootModuleInstance ),
207+ states .DeposedKey ("00000000" ),
208+ & states.ResourceInstanceObjectSrc {
209+ Status : states .ObjectReady ,
210+ AttrsJSON : []byte (`{
211+ "ami": "bar",
212+ "network_interface": [{
213+ "device_index": 0,
214+ "description": "Main network interface"
215+ }]
216+ }` ),
217+ },
218+ addrs.ProviderConfig {
219+ Type : "test" ,
220+ }.Absolute (addrs .RootModuleInstance ),
221+ )
222+ }))
223+ b .CLI = cli .NewMockUi ()
224+ outDir := testTempDir (t )
225+ defer os .RemoveAll (outDir )
226+ planPath := filepath .Join (outDir , "plan.tfplan" )
227+ op , configCleanup := testOperationPlan (t , "./test-fixtures/plan" )
228+ defer configCleanup ()
229+ op .PlanRefresh = true
230+ op .PlanOutPath = planPath
231+ cfg := cty .ObjectVal (map [string ]cty.Value {
232+ "path" : cty .StringVal (b .StatePath ),
233+ })
234+ cfgRaw , err := plans .NewDynamicValue (cfg , cfg .Type ())
235+ if err != nil {
236+ t .Fatal (err )
237+ }
238+ op .PlanOutBackend = & plans.Backend {
239+ // Just a placeholder so that we can generate a valid plan file.
240+ Type : "local" ,
241+ Config : cfgRaw ,
242+ }
243+ run , err := b .Operation (context .Background (), op )
244+ if err != nil {
245+ t .Fatalf ("bad: %s" , err )
246+ }
247+ <- run .Done ()
248+ if run .Result != backend .OperationSuccess {
249+ t .Fatalf ("plan operation failed" )
250+ }
251+ if ! p .ReadResourceCalled {
252+ t .Fatal ("ReadResource should be called" )
253+ }
254+ if run .PlanEmpty {
255+ t .Fatal ("plan should not be empty" )
256+ }
257+
258+ // The deposed object and the current object are distinct, so our
259+ // plan includes separate actions for each of them. This strange situation
260+ // is not common: it should arise only if Terraform fails during
261+ // a create-before-destroy when the create hasn't completed yet but
262+ // in a severe way that prevents the previous object from being restored
263+ // as "current".
264+ //
265+ // However, that situation was more common in some earlier Terraform
266+ // versions where deposed objects were not managed properly, so this
267+ // can arise when upgrading from an older version with deposed objects
268+ // already in the state.
269+ //
270+ // This is one of the few cases where we expose the idea of "deposed" in
271+ // the UI, including the user-unfriendly "deposed key" (00000000 in this
272+ // case) just so that users can correlate this with what they might
273+ // see in `terraform show` and in the subsequent apply output, because
274+ // it's also possible for there to be _multiple_ deposed objects, in the
275+ // unlikely event that create_before_destroy _keeps_ crashing across
276+ // subsequent runs.
277+ expectedOutput := `An execution plan has been generated and is shown below.
278+ Resource actions are indicated with the following symbols:
279+ + create
280+ - destroy
281+
282+ Terraform will perform the following actions:
283+
284+ # test_instance.foo will be created
285+ + resource "test_instance" "foo" {
286+ + ami = "bar"
287+
288+ + network_interface {
289+ + description = "Main network interface"
290+ + device_index = 0
291+ }
292+ }
293+
294+ # test_instance.foo (deposed object 00000000) will be destroyed
295+ - resource "test_instance" "foo" {
296+ - ami = "bar" -> null
297+
298+ - network_interface {
299+ - description = "Main network interface" -> null
300+ - device_index = 0 -> null
301+ }
302+ }
303+
304+ Plan: 1 to add, 0 to change, 1 to destroy.`
305+ output := b .CLI .(* cli.MockUi ).OutputWriter .String ()
306+ if ! strings .Contains (output , expectedOutput ) {
307+ t .Fatalf ("Unexpected output:\n %s\n \n want output containing:\n %s" , output , expectedOutput )
308+ }
309+ }
310+
196311func TestLocal_planTainted_createBeforeDestroy (t * testing.T ) {
197312 b , cleanup := TestLocal (t )
198313 defer cleanup ()
0 commit comments