@@ -24,6 +24,7 @@ import (
2424 "log"
2525 "net/http"
2626 "reflect"
27+ "strings"
2728 "time"
2829
2930 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
@@ -37,6 +38,7 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
3738 return & schema.Resource {
3839 Create : resourceComputeFirewallPolicyAssociationCreate ,
3940 Read : resourceComputeFirewallPolicyAssociationRead ,
41+ Update : resourceComputeFirewallPolicyAssociationUpdate ,
4042 Delete : resourceComputeFirewallPolicyAssociationDelete ,
4143
4244 Importer : & schema.ResourceImporter {
@@ -45,6 +47,7 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
4547
4648 Timeouts : & schema.ResourceTimeout {
4749 Create : schema .DefaultTimeout (20 * time .Minute ),
50+ Update : schema .DefaultTimeout (20 * time .Minute ),
4851 Delete : schema .DefaultTimeout (20 * time .Minute ),
4952 },
5053
@@ -63,9 +66,14 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
6366 "firewall_policy" : {
6467 Type : schema .TypeString ,
6568 Required : true ,
66- ForceNew : true ,
6769 DiffSuppressFunc : tpgresource .CompareResourceNames ,
68- Description : `The firewall policy of the resource.` ,
70+ Description : `The firewall policy of the resource.
71+
72+ This field can be updated to refer to a different Firewall Policy, which will create a new association from that new
73+ firewall policy with the flag to override the existing attachmentTarget's policy association.
74+
75+ **Note** Due to potential risks with this operation it is *highly* recommended to use the 'create_before_destroy' life cycle option
76+ on your exisiting firewall policy so as to prevent a situation where your attachment target has no associated policy.` ,
6977 },
7078 "name" : {
7179 Type : schema .TypeString ,
@@ -212,6 +220,84 @@ func resourceComputeFirewallPolicyAssociationRead(d *schema.ResourceData, meta i
212220 return nil
213221}
214222
223+ func resourceComputeFirewallPolicyAssociationUpdate (d * schema.ResourceData , meta interface {}) error {
224+ config := meta .(* transport_tpg.Config )
225+ userAgent , err := tpgresource .GenerateUserAgentString (d , config .UserAgent )
226+ if err != nil {
227+ return err
228+ }
229+
230+ billingProject := ""
231+
232+ obj := make (map [string ]interface {})
233+ nameProp , err := expandComputeFirewallPolicyAssociationName (d .Get ("name" ), d , config )
234+ if err != nil {
235+ return err
236+ } else if v , ok := d .GetOkExists ("name" ); ! tpgresource .IsEmptyValue (reflect .ValueOf (v )) && (ok || ! reflect .DeepEqual (v , nameProp )) {
237+ obj ["name" ] = nameProp
238+ }
239+ attachmentTargetProp , err := expandComputeFirewallPolicyAssociationAttachmentTarget (d .Get ("attachment_target" ), d , config )
240+ if err != nil {
241+ return err
242+ } else if v , ok := d .GetOkExists ("attachment_target" ); ! tpgresource .IsEmptyValue (reflect .ValueOf (v )) && (ok || ! reflect .DeepEqual (v , attachmentTargetProp )) {
243+ obj ["attachmentTarget" ] = attachmentTargetProp
244+ }
245+ firewallPolicyProp , err := expandComputeFirewallPolicyAssociationFirewallPolicy (d .Get ("firewall_policy" ), d , config )
246+ if err != nil {
247+ return err
248+ } else if v , ok := d .GetOkExists ("firewall_policy" ); ! tpgresource .IsEmptyValue (reflect .ValueOf (v )) && (ok || ! reflect .DeepEqual (v , firewallPolicyProp )) {
249+ obj ["firewallPolicy" ] = firewallPolicyProp
250+ }
251+
252+ url , err := tpgresource .ReplaceVars (d , config , "{{ComputeBasePath}}locations/global/firewallPolicies/{{firewall_policy}}/addAssociation?replaceExistingAssociation=true" )
253+ if err != nil {
254+ return err
255+ }
256+
257+ log .Printf ("[DEBUG] Updating FirewallPolicyAssociation %q: %#v" , d .Id (), obj )
258+ headers := make (http.Header )
259+
260+ // err == nil indicates that the billing_project value was found
261+ if bp , err := tpgresource .GetBillingProject (d , config ); err == nil {
262+ billingProject = bp
263+ }
264+
265+ res , err := transport_tpg .SendRequest (transport_tpg.SendRequestOptions {
266+ Config : config ,
267+ Method : "POST" ,
268+ Project : billingProject ,
269+ RawURL : url ,
270+ UserAgent : userAgent ,
271+ Body : obj ,
272+ Timeout : d .Timeout (schema .TimeoutUpdate ),
273+ Headers : headers ,
274+ })
275+ //following section is the area of the `custom_update` function for this resource that is different
276+ //`custom_update` was necessary over a `post_update"` because of the change to the error handler
277+ if err != nil {
278+ //before failing an update, restores the old firewall_policy value to prevent terraform state becoming broken
279+ parts := strings .Split (d .Id (), "/" )
280+ oldPolicyPath := "locations/global/firewallPolicies/" + parts [len (parts )- 3 ]
281+ d .Set ("firewall_policy" , oldPolicyPath )
282+ return fmt .Errorf ("Error updating FirewallPolicyAssociation %q: %s" , d .Id (), err )
283+ } else {
284+ log .Printf ("[DEBUG] Finished updating FirewallPolicyAssociation %q: %#v" , d .Id (), res )
285+ }
286+
287+ // store the ID now, needed because this update function is changing a normally immutable URL parameter field
288+ id , err := tpgresource .ReplaceVars (d , config , "locations/global/firewallPolicies/{{firewall_policy}}/associations/{{name}}" )
289+ if err != nil {
290+ return fmt .Errorf ("Error constructing id: %s" , err )
291+ }
292+ d .SetId (id )
293+
294+ //following the swapover briefly both associations exist and this can cause operations to fail
295+ time .Sleep (60 * time .Second )
296+ //end `custom_update` changed zone
297+
298+ return resourceComputeFirewallPolicyAssociationRead (d , meta )
299+ }
300+
215301func resourceComputeFirewallPolicyAssociationDelete (d * schema.ResourceData , meta interface {}) error {
216302 config := meta .(* transport_tpg.Config )
217303 userAgent , err := tpgresource .GenerateUserAgentString (d , config .UserAgent )
0 commit comments