Skip to content

Commit b36deea

Browse files
akemner-figmaclaude
andcommitted
okta_push_group: tolerate 404 (remove from state on read, idempotent delete)
When an okta_push_group mapping (or its source group) is deleted outside Terraform, Read returned the 404 as a fatal error, so plan/refresh failed and the resource was never dropped from state — the only recovery was a manual `terraform state rm`. Delete had the same gap: it would error if the mapping was already gone. Read now removes the resource from state on a 404; Delete now treats a 404 as already-deleted. Both match the idiom used by other framework resources (okta_identity_source_group, okta_group_owners). Non-404 errors are unchanged. Adds TestAccResourceOktaPushGroup_disappears covering the out-of-band deletion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3e47611 commit b36deea

2 files changed

Lines changed: 77 additions & 3 deletions

File tree

okta/services/idaas/resource_okta_push_group.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package idaas
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67
"strings"
78

89
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -224,8 +225,12 @@ func (r *pushGroupResource) Read(ctx context.Context, req resource.ReadRequest,
224225
return
225226
}
226227

227-
groupPushMapping, _, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.GetGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).Execute()
228+
groupPushMapping, httpResp, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.GetGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).Execute()
228229
if err != nil {
230+
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
231+
resp.State.RemoveResource(ctx)
232+
return
233+
}
229234
resp.Diagnostics.AddError("Error reading Okta push group mapping ", err.Error())
230235
return
231236
}
@@ -290,17 +295,23 @@ func (r *pushGroupResource) Delete(ctx context.Context, req resource.DeleteReque
290295
}
291296

292297
if state.Status.ValueString() != "INACTIVE" {
293-
_, _, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.UpdateGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).Body(v6okta.UpdateGroupPushMappingRequest{
298+
_, httpResp, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.UpdateGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).Body(v6okta.UpdateGroupPushMappingRequest{
294299
Status: "INACTIVE",
295300
}).Execute()
296301
if err != nil {
302+
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
303+
return
304+
}
297305
resp.Diagnostics.AddError("failed to delete push group mapping: ", err.Error())
298306
return
299307
}
300308
}
301309

302-
_, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.DeleteGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).DeleteTargetGroup(state.DeleteTargetGroupOnDestroy.ValueBool()).Execute()
310+
httpResp, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.DeleteGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).DeleteTargetGroup(state.DeleteTargetGroupOnDestroy.ValueBool()).Execute()
303311
if err != nil {
312+
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
313+
return
314+
}
304315
resp.Diagnostics.AddError("failed to delete push group mapping: ", err.Error())
305316
return
306317
}

okta/services/idaas/resource_okta_push_group_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package idaas_test
22

33
import (
4+
"context"
45
"fmt"
56
"testing"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
10+
v6okta "github.com/okta/okta-sdk-golang/v6/okta"
811
"github.com/okta/terraform-provider-okta/okta/acctest"
912
"github.com/okta/terraform-provider-okta/okta/resources"
1013
)
@@ -45,6 +48,66 @@ func TestAccResourceOktaPushGroup_crud(t *testing.T) {
4548
})
4649
}
4750

51+
// TestAccResourceOktaPushGroup_disappears verifies that when a push group
52+
// mapping is removed outside Terraform, the next refresh drops it from state
53+
// and plans to recreate it, rather than failing the plan with a 404 read error.
54+
func TestAccResourceOktaPushGroup_disappears(t *testing.T) {
55+
resourceName := fmt.Sprintf("%s.sample", resources.OktaIDaaSPushGroup)
56+
mgr := newFixtureManager("resources", resources.OktaIDaaSPushGroup, t.Name())
57+
config := mgr.GetFixtures("okta_push_group.tf", t)
58+
59+
acctest.OktaResourceTest(t, resource.TestCase{
60+
PreCheck: acctest.AccPreCheck(t),
61+
ErrorCheck: testAccErrorChecks(t),
62+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactoriesForTestAcc(t),
63+
CheckDestroy: nil,
64+
Steps: []resource.TestStep{
65+
{
66+
Config: config,
67+
Check: resource.ComposeTestCheckFunc(
68+
resource.TestCheckResourceAttrSet(resourceName, "id"),
69+
resource.TestCheckResourceAttrSet(resourceName, "app_id"),
70+
),
71+
},
72+
{
73+
Config: config,
74+
Check: resource.ComposeTestCheckFunc(
75+
clickOpsDeletePushGroupMapping(resourceName),
76+
),
77+
ExpectNonEmptyPlan: true,
78+
},
79+
},
80+
})
81+
}
82+
83+
// clickOpsDeletePushGroupMapping deletes the push group mapping directly via the
84+
// API, simulating the mapping (or its source group) being removed outside
85+
// Terraform. The Okta API requires the mapping to be INACTIVE before deletion.
86+
// The pushed target group is deleted too so it isn't orphaned across runs (the
87+
// resource's own Delete never runs once state self-heals on the 404).
88+
func clickOpsDeletePushGroupMapping(resourceName string) resource.TestCheckFunc {
89+
return func(s *terraform.State) error {
90+
rs, ok := s.RootModule().Resources[resourceName]
91+
if !ok {
92+
return fmt.Errorf("resource not found: %s", resourceName)
93+
}
94+
appID := rs.Primary.Attributes["app_id"]
95+
mappingID := rs.Primary.ID
96+
client := iDaaSAPIClientForTestUtil.OktaSDKClientV6()
97+
ctx := context.Background()
98+
99+
if _, _, err := client.GroupPushMappingAPI.UpdateGroupPushMapping(ctx, appID, mappingID).
100+
Body(v6okta.UpdateGroupPushMappingRequest{Status: "INACTIVE"}).Execute(); err != nil {
101+
return fmt.Errorf("API: unable to deactivate push group mapping %q: %+v", mappingID, err)
102+
}
103+
if _, err := client.GroupPushMappingAPI.DeleteGroupPushMapping(ctx, appID, mappingID).
104+
DeleteTargetGroup(true).Execute(); err != nil {
105+
return fmt.Errorf("API: unable to delete push group mapping %q: %+v", mappingID, err)
106+
}
107+
return nil
108+
}
109+
}
110+
48111
func TestAccResourceOktaPushGroup_ad(t *testing.T) {
49112
resourceName := fmt.Sprintf("%s.sample", resources.OktaIDaaSPushGroup)
50113
mgr := newFixtureManager("resources", resources.OktaIDaaSPushGroup, t.Name())

0 commit comments

Comments
 (0)