@@ -97,20 +97,95 @@ func (c *CloudClient) CreatePolicy(policy *models.Policy) (*PolicyWithETag, erro
9797func (c * CloudClient ) UpdatePolicy (policy * models.Policy , etag string ) (* PolicyWithETag , error ) {
9898 path := fmt .Sprintf ("/ps/%s/access/policies/%s" , policy .PermissionsSystemID , policy .ID )
9999
100- // Create a direct PUT request without using UpdateResource
101- req , err := c .NewRequest (http .MethodPut , path , policy )
102- if err != nil {
103- return nil , fmt .Errorf ("failed to create request: %w" , err )
100+ // Define a function to get the latest ETag
101+ getLatestETag := func () (string , error ) {
102+ getReq , err := c .NewRequest (http .MethodGet , path , nil )
103+ if err != nil {
104+ return "" , fmt .Errorf ("failed to create GET request: %w" , err )
105+ }
106+
107+ getResp , err := c .Do (getReq )
108+ if err != nil {
109+ return "" , fmt .Errorf ("failed to send GET request: %w" , err )
110+ }
111+ defer func () {
112+ if getResp .Response .Body != nil {
113+ _ = getResp .Response .Body .Close ()
114+ }
115+ }()
116+
117+ if getResp .Response .StatusCode != http .StatusOK {
118+ return "" , fmt .Errorf ("failed to get latest ETag, status: %d" , getResp .Response .StatusCode )
119+ }
120+
121+ // Extract ETag from response header
122+ latestETag := getResp .ETag
123+ return latestETag , nil
104124 }
105125
106- // Only set If-Match header if we have a non-empty ETag
107- if etag != "" {
108- req .Header .Set ("If-Match" , etag )
126+ // Try update with provided ETag
127+ updateWithETag := func (currentETag string ) (* ResponseWithETag , error ) {
128+ req , err := c .NewRequest (http .MethodPut , path , policy )
129+ if err != nil {
130+ return nil , fmt .Errorf ("failed to create request: %w" , err )
131+ }
132+
133+ // Only set If-Match header if we have a non-empty ETag
134+ if currentETag != "" {
135+ req .Header .Set ("If-Match" , currentETag )
136+ }
137+
138+ respWithETag , err := c .Do (req )
139+ if err != nil {
140+ return nil , fmt .Errorf ("failed to send request: %w" , err )
141+ }
142+
143+ return respWithETag , nil
109144 }
110145
111- respWithETag , err := c .Do (req )
146+ // First attempt with the provided ETag
147+ respWithETag , err := updateWithETag (etag )
112148 if err != nil {
113- return nil , fmt .Errorf ("failed to send request: %w" , err )
149+ return nil , err
150+ }
151+
152+ // Handle the 412 Precondition Failed error by retrying with the latest ETag
153+ if respWithETag .Response .StatusCode == http .StatusPreconditionFailed {
154+ // Close the body of the first response
155+ if respWithETag .Response .Body != nil {
156+ _ = respWithETag .Response .Body .Close ()
157+ }
158+
159+ // Get the latest ETag
160+ latestETag , err := getLatestETag ()
161+ if err != nil {
162+ return nil , fmt .Errorf ("failed to get latest ETag for retry: %w" , err )
163+ }
164+
165+ // Retry the update with the latest ETag
166+ respWithETag , err = updateWithETag (latestETag )
167+ if err != nil {
168+ return nil , err
169+ }
170+ }
171+
172+ // Handle 409 Conflict error, FGAM config changes, by retrying with fresh ETag
173+ if respWithETag .Response .StatusCode == http .StatusConflict {
174+ if respWithETag .Response .Body != nil {
175+ _ = respWithETag .Response .Body .Close ()
176+ }
177+
178+ // Get the latest ETag after FGAM configuration change
179+ latestETag , err := getLatestETag ()
180+ if err != nil {
181+ return nil , fmt .Errorf ("failed to get latest ETag after FGAM configuration change: %w" , err )
182+ }
183+
184+ // Retry the update with the fresh ETag
185+ respWithETag , err = updateWithETag (latestETag )
186+ if err != nil {
187+ return nil , err
188+ }
114189 }
115190
116191 // Keep the response body for potential error reporting
@@ -127,53 +202,54 @@ func (c *CloudClient) UpdatePolicy(policy *models.Policy, etag string) (*PolicyW
127202 }
128203 }()
129204
130- if respWithETag .Response .StatusCode != http .StatusOK {
131- // If it's a 404 error, attempt to recreate the resource
132- if respWithETag .Response .StatusCode == http .StatusNotFound {
133- // Recreate the policy using POST to the base endpoint
134- createPath := fmt .Sprintf ("/ps/%s/access/policies" , policy .PermissionsSystemID )
135- originalID := policy .ID
136-
137- createReq , err := c .NewRequest (http .MethodPost , createPath , policy )
138- if err != nil {
139- return nil , fmt .Errorf ("failed to create request for recreation: %w" , err )
140- }
205+ // Handle 404 Not Found
206+ if respWithETag .Response .StatusCode == http .StatusNotFound {
207+ // Recreate the policy using POST to the base endpoint
208+ createPath := fmt .Sprintf ("/ps/%s/access/policies" , policy .PermissionsSystemID )
209+ originalID := policy .ID
141210
142- createResp , err := c .Do ( createReq )
143- if err != nil {
144- return nil , fmt .Errorf ("failed to send create request for recreation: %w" , err )
145- }
211+ createReq , err := c .NewRequest ( http . MethodPost , createPath , policy )
212+ if err != nil {
213+ return nil , fmt .Errorf ("failed to create request for recreation: %w" , err )
214+ }
146215
147- defer func () {
148- if createResp .Response .Body != nil {
149- _ = createResp .Response .Body .Close ()
150- }
151- }()
216+ createResp , err := c .Do (createReq )
217+ if err != nil {
218+ return nil , fmt .Errorf ("failed to send create request for recreation: %w" , err )
219+ }
152220
153- if createResp .Response .StatusCode != http .StatusCreated {
154- return nil , NewAPIError (createResp )
221+ defer func () {
222+ if createResp .Response .Body != nil {
223+ _ = createResp .Response .Body .Close ()
155224 }
225+ }()
156226
157- // Decode the created policy
158- var createdPolicy models.Policy
159- if err := json .NewDecoder (createResp .Response .Body ).Decode (& createdPolicy ); err != nil {
160- return nil , fmt .Errorf ("failed to decode recreated policy: %w" , err )
161- }
227+ if createResp .Response .StatusCode != http .StatusCreated {
228+ return nil , NewAPIError (createResp )
229+ }
162230
163- // Create the result with the original ID to maintain consistency
164- result := & PolicyWithETag {
165- Policy : & createdPolicy ,
166- ETag : createResp . ETag ,
167- }
231+ // Decode the created policy
232+ var createdPolicy models. Policy
233+ if err := json . NewDecoder ( createResp . Response . Body ). Decode ( & createdPolicy ); err != nil {
234+ return nil , fmt . Errorf ( "failed to decode recreated policy: %w" , err )
235+ }
168236
169- // Force the right ID to maintain Terraform state consistency
170- if result .Policy .ID != originalID {
171- result .Policy .ID = originalID
172- }
237+ // Create the result with the original ID to maintain consistency
238+ result := & PolicyWithETag {
239+ Policy : & createdPolicy ,
240+ ETag : createResp .ETag ,
241+ }
173242
174- return result , nil
243+ // Force the right ID to maintain Terraform state consistency
244+ if result .Policy .ID != originalID {
245+ result .Policy .ID = originalID
175246 }
176247
248+ return result , nil
249+ }
250+
251+ // Handle other error status codes
252+ if respWithETag .Response .StatusCode != http .StatusOK {
177253 return nil , NewAPIError (respWithETag )
178254 }
179255
0 commit comments