@@ -24,6 +24,7 @@ import (
24
24
"log"
25
25
"net/http"
26
26
"reflect"
27
+ "strings"
27
28
"time"
28
29
29
30
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
@@ -37,6 +38,7 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
37
38
return & schema.Resource {
38
39
Create : resourceComputeFirewallPolicyAssociationCreate ,
39
40
Read : resourceComputeFirewallPolicyAssociationRead ,
41
+ Update : resourceComputeFirewallPolicyAssociationUpdate ,
40
42
Delete : resourceComputeFirewallPolicyAssociationDelete ,
41
43
42
44
Importer : & schema.ResourceImporter {
@@ -45,6 +47,7 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
45
47
46
48
Timeouts : & schema.ResourceTimeout {
47
49
Create : schema .DefaultTimeout (20 * time .Minute ),
50
+ Update : schema .DefaultTimeout (20 * time .Minute ),
48
51
Delete : schema .DefaultTimeout (20 * time .Minute ),
49
52
},
50
53
@@ -63,9 +66,14 @@ func ResourceComputeFirewallPolicyAssociation() *schema.Resource {
63
66
"firewall_policy" : {
64
67
Type : schema .TypeString ,
65
68
Required : true ,
66
- ForceNew : true ,
67
69
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.` ,
69
77
},
70
78
"name" : {
71
79
Type : schema .TypeString ,
@@ -212,6 +220,84 @@ func resourceComputeFirewallPolicyAssociationRead(d *schema.ResourceData, meta i
212
220
return nil
213
221
}
214
222
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
+
215
301
func resourceComputeFirewallPolicyAssociationDelete (d * schema.ResourceData , meta interface {}) error {
216
302
config := meta .(* transport_tpg.Config )
217
303
userAgent , err := tpgresource .GenerateUserAgentString (d , config .UserAgent )
0 commit comments