From b374fc837b88cb0fa78148b204aae9d4076fdc74 Mon Sep 17 00:00:00 2001 From: Sander Aernouts Date: Tue, 31 Mar 2026 16:47:11 +0200 Subject: [PATCH] azurerm_firewall_policy_rule_collection_group - fix timeout expiring while waiting for policy lock When multiple firewall policy rule collection groups target the same firewall policy, Terraform processes them in parallel but Azure enforces serial processing (responding with 409 AnotherOperationInProgress). The provider already serializes these operations using locks.ByName(), but the timeout context was created before the lock was acquired. This meant time spent waiting for the lock consumed the 30-minute timeout budget, causing later operations to fail with context.DeadlineExceeded. Move lock acquisition before the timeout context creation in both CreateUpdate and Delete so each operation gets its full timeout budget regardless of how long it waited for the lock. --- ...l_policy_rule_collection_group_resource.go | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/internal/services/firewall/firewall_policy_rule_collection_group_resource.go b/internal/services/firewall/firewall_policy_rule_collection_group_resource.go index a1d8c2306de9..1e74c71e9742 100644 --- a/internal/services/firewall/firewall_policy_rule_collection_group_resource.go +++ b/internal/services/firewall/firewall_policy_rule_collection_group_resource.go @@ -460,8 +460,6 @@ func resourceFirewallPolicyRuleCollectionGroup() *pluginsdk.Resource { func resourceFirewallPolicyRuleCollectionGroupCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.FirewallPolicyRuleCollectionGroups - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) - defer cancel() policyId, err := firewallpolicies.ParseFirewallPolicyID(d.Get("firewall_policy_id").(string)) if err != nil { @@ -470,6 +468,16 @@ func resourceFirewallPolicyRuleCollectionGroupCreateUpdate(d *pluginsdk.Resource id := firewallpolicyrulecollectiongroups.NewRuleCollectionGroupID(policyId.SubscriptionId, policyId.ResourceGroupName, policyId.FirewallPolicyName, d.Get("name").(string)) + // NOTE: Acquire the lock before creating the timeout context. Azure enforces serial processing + // on firewall policy rule collection groups within the same policy, responding with 409 + // AnotherOperationInProgress for concurrent requests. The timeout context must be created after + // the lock is acquired so that time spent waiting for the lock does not consume the timeout budget. + locks.ByName(policyId.FirewallPolicyName, AzureFirewallPolicyResourceName) + defer locks.UnlockByName(policyId.FirewallPolicyName, AzureFirewallPolicyResourceName) + + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + if d.IsNewResource() { resp, err := client.Get(ctx, id) if err != nil { @@ -483,9 +491,6 @@ func resourceFirewallPolicyRuleCollectionGroupCreateUpdate(d *pluginsdk.Resource } } - locks.ByName(policyId.FirewallPolicyName, AzureFirewallPolicyResourceName) - defer locks.UnlockByName(policyId.FirewallPolicyName, AzureFirewallPolicyResourceName) - param := firewallpolicyrulecollectiongroups.FirewallPolicyRuleCollectionGroup{ Properties: &firewallpolicyrulecollectiongroups.FirewallPolicyRuleCollectionGroupProperties{ Priority: pointer.To(int64(d.Get("priority").(int))), @@ -568,17 +573,22 @@ func resourceFirewallPolicyRuleCollectionGroupSetFlatten(d *pluginsdk.ResourceDa func resourceFirewallPolicyRuleCollectionGroupDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.FirewallPolicyRuleCollectionGroups - ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) - defer cancel() id, err := firewallpolicyrulecollectiongroups.ParseRuleCollectionGroupID(d.Id()) if err != nil { return err } + // NOTE: Acquire the lock before creating the timeout context. Azure enforces serial processing + // on firewall policy rule collection groups within the same policy, responding with 409 + // AnotherOperationInProgress for concurrent requests. The timeout context must be created after + // the lock is acquired so that time spent waiting for the lock does not consume the timeout budget. locks.ByName(id.FirewallPolicyName, AzureFirewallPolicyResourceName) defer locks.UnlockByName(id.FirewallPolicyName, AzureFirewallPolicyResourceName) + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + if err = client.DeleteThenPoll(ctx, *id); err != nil { return fmt.Errorf("deleting %s: %+v", id, err) }