@@ -4082,3 +4082,192 @@ func TestContext2Apply_excludeListResources(t *testing.T) {
40824082 t .Fatal ("expected error" )
40834083 }
40844084}
4085+
4086+ func TestContext2Apply_errorDestroyWithIdentity (t * testing.T ) {
4087+ m := testModule (t , "empty" )
4088+ p := testProvider ("test" )
4089+
4090+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& providerSchema {
4091+ ResourceTypes : map [string ]* configschema.Block {
4092+ "test_resource" : {
4093+ Attributes : map [string ]* configschema.Attribute {
4094+ "id" : {Type : cty .String , Optional : true },
4095+ },
4096+ },
4097+ },
4098+ IdentityTypes : map [string ]* configschema.Object {
4099+ "test_resource" : {
4100+ Attributes : map [string ]* configschema.Attribute {
4101+ "id" : {
4102+ Type : cty .String ,
4103+ Required : true ,
4104+ },
4105+ },
4106+ Nesting : configschema .NestingSingle ,
4107+ },
4108+ },
4109+ })
4110+ p .PlanResourceChangeFn = func (req providers.PlanResourceChangeRequest ) providers.PlanResourceChangeResponse {
4111+ // Should actually be called for this test, because Terraform Core
4112+ // constructs the plan for a destroy operation itself.
4113+ return providers.PlanResourceChangeResponse {
4114+ PlannedState : req .ProposedNewState ,
4115+ }
4116+ }
4117+ value := cty .ObjectVal (map [string ]cty.Value {
4118+ "id" : cty .StringVal ("baz" ),
4119+ })
4120+ identity := cty .ObjectVal (map [string ]cty.Value {
4121+ "id" : cty .StringVal ("baz" ),
4122+ })
4123+ p .ApplyResourceChangeFn = func (req providers.ApplyResourceChangeRequest ) providers.ApplyResourceChangeResponse {
4124+ // The apply (in this case, a destroy) always fails, so we can verify
4125+ // that the object stays in the state after a destroy fails even though
4126+ // we aren't returning a new state object here.
4127+ return providers.ApplyResourceChangeResponse {
4128+ NewState : value ,
4129+ NewIdentity : identity ,
4130+ Diagnostics : tfdiags .Diagnostics (nil ).Append (fmt .Errorf ("failed" )),
4131+ }
4132+ }
4133+
4134+ ctx := testContext2 (t , & ContextOpts {
4135+ Providers : map [addrs.Provider ]providers.Factory {
4136+ addrs .NewDefaultProvider ("test" ): testProviderFuncFixed (p ),
4137+ },
4138+ })
4139+
4140+ state := states .BuildState (func (ss * states.SyncState ) {
4141+ ss .SetResourceInstanceCurrent (
4142+ addrs.Resource {
4143+ Mode : addrs .ManagedResourceMode ,
4144+ Type : "test_resource" ,
4145+ Name : "test" ,
4146+ }.Instance (addrs .NoKey ).Absolute (addrs .RootModuleInstance ),
4147+ & states.ResourceInstanceObjectSrc {
4148+ Status : states .ObjectReady ,
4149+ AttrsJSON : []byte (`{"id":"baz"}` ),
4150+ IdentityJSON : []byte (`{"id":"baz"}` ),
4151+ },
4152+ addrs.AbsProviderConfig {
4153+ Provider : addrs .NewDefaultProvider ("test" ),
4154+ Module : addrs .RootModule ,
4155+ },
4156+ )
4157+ })
4158+ plan , diags := ctx .Plan (m , state , DefaultPlanOpts )
4159+ tfdiags .AssertNoErrors (t , diags )
4160+
4161+ state , diags = ctx .Apply (plan , m , nil )
4162+ if ! diags .HasErrors () {
4163+ t .Fatal ("should have error" )
4164+ }
4165+
4166+ schema := p .GetProviderSchemaResponse .ResourceTypes ["test_resource" ]
4167+ resourceInstanceStateSrc := state .Modules ["" ].Resources ["test_resource.test" ].Instance (addrs .NoKey ).Current
4168+ resourceInstanceState , err := resourceInstanceStateSrc .Decode (schema )
4169+ if err != nil {
4170+ t .Fatalf ("failed to decode resource instance state: %s" , err )
4171+ }
4172+
4173+ if ! resourceInstanceState .Value .RawEquals (value ) {
4174+ t .Fatalf ("expected value to still be present in state, but got: %s" , resourceInstanceState .Value .GoString ())
4175+ }
4176+ if ! resourceInstanceState .Identity .RawEquals (identity ) {
4177+ t .Fatalf ("expected identity to still be present in state, but got: %s" , resourceInstanceState .Identity .GoString ())
4178+ }
4179+ }
4180+
4181+ func TestContext2Apply_SensitivityChangeWithIdentity (t * testing.T ) {
4182+ m := testModuleInline (t , map [string ]string {
4183+ "main.tf" : `
4184+ variable "sensitive_var" {
4185+ default = "hello"
4186+ sensitive = true
4187+ }
4188+
4189+ resource "test_resource" "foo" {
4190+ value = var.sensitive_var
4191+ }` ,
4192+ })
4193+
4194+ p := testProvider ("test" )
4195+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& providerSchema {
4196+ ResourceTypes : map [string ]* configschema.Block {
4197+ "test_resource" : {
4198+ Attributes : map [string ]* configschema.Attribute {
4199+ "id" : {Type : cty .String , Computed : true },
4200+ "value" : {Type : cty .String , Optional : true },
4201+ "sensitive_value" : {Type : cty .String , Sensitive : true , Optional : true },
4202+ },
4203+ },
4204+ },
4205+ IdentityTypes : map [string ]* configschema.Object {
4206+ "test_resource" : {
4207+ Attributes : map [string ]* configschema.Attribute {
4208+ "id" : {
4209+ Type : cty .String ,
4210+ Required : true ,
4211+ },
4212+ },
4213+ Nesting : configschema .NestingSingle ,
4214+ },
4215+ },
4216+ })
4217+ p .PlanResourceChangeFn = testDiffFn
4218+
4219+ ctx := testContext2 (t , & ContextOpts {
4220+ Providers : map [addrs.Provider ]providers.Factory {
4221+ addrs .NewDefaultProvider ("test" ): testProviderFuncFixed (p ),
4222+ },
4223+ })
4224+
4225+ state := states .BuildState (func (s * states.SyncState ) {
4226+ s .SetResourceInstanceCurrent (
4227+ addrs.Resource {
4228+ Mode : addrs .ManagedResourceMode ,
4229+ Type : "test_resource" ,
4230+ Name : "foo" ,
4231+ }.Instance (addrs .NoKey ).Absolute (addrs .RootModuleInstance ),
4232+ & states.ResourceInstanceObjectSrc {
4233+ Status : states .ObjectReady ,
4234+ AttrsJSON : []byte (`{"id":"foo", "value":"hello"}` ),
4235+ IdentityJSON : []byte (`{"id":"baz"}` ),
4236+ // No AttrSensitivePaths present
4237+ },
4238+ addrs.AbsProviderConfig {
4239+ Provider : addrs .NewDefaultProvider ("test" ),
4240+ Module : addrs .RootModule ,
4241+ },
4242+ )
4243+ })
4244+
4245+ plan , diags := ctx .Plan (m , state , SimplePlanOpts (plans .NormalMode , testInputValuesUnset (m .Module .Variables )))
4246+ tfdiags .AssertNoErrors (t , diags )
4247+
4248+ addr := mustResourceInstanceAddr ("test_resource.foo" )
4249+
4250+ state , diags = ctx .Apply (plan , m , nil )
4251+ tfdiags .AssertNoErrors (t , diags )
4252+
4253+ fooState := state .ResourceInstance (addr )
4254+
4255+ if len (fooState .Current .AttrSensitivePaths ) != 2 {
4256+ t .Fatalf ("wrong number of sensitive paths, expected 2, got, %v" , len (fooState .Current .AttrSensitivePaths ))
4257+ }
4258+
4259+ for _ , path := range fooState .Current .AttrSensitivePaths {
4260+ switch {
4261+ case path .Equals (cty .GetAttrPath ("value" )):
4262+ case path .Equals (cty .GetAttrPath ("sensitive_value" )):
4263+ default :
4264+ t .Errorf ("unexpected sensitive path: %#v" , path )
4265+ return
4266+ }
4267+ }
4268+
4269+ expectedIdentity := `{"id":"baz"}`
4270+ if string (fooState .Current .IdentityJSON ) != expectedIdentity {
4271+ t .Fatalf ("missing identity in state, got %q" , fooState .Current .IdentityJSON )
4272+ }
4273+ }
0 commit comments