@@ -203,6 +203,9 @@ func (r *{{ resourceStructName }}) ImportState(ctx context.Context, req resource
203
203
{{- /* Done */ -}}`
204
204
205
205
const resourceCreateManyFunction = `
206
+ {{ $resourceSDKStructName := printf "%s.%s" .resourceSDKName .EntryOrConfig }}
207
+ {{ $resourceTFStructName := printf "%s%sObject" .structName .ListAttribute.CamelCase }}
208
+
206
209
var state, createdState {{ .structName }}Model
207
210
resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...)
208
211
if resp.Diagnostics.HasError() {
@@ -220,12 +223,12 @@ svc := {{ .resourceSDKName }}.NewService(r.client)
220
223
var location {{ .resourceSDKName }}.Location
221
224
{{ RenderLocationsStateToPango "state.Location" "location" }}
222
225
223
- var elements []{{ .structName }}{{ .ListAttribute.CamelCase }}Object
226
+ var elements []{{ $resourceTFStructName }}
224
227
state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)
225
- entries := make([]*{{ .resourceSDKName }}.{{ .EntryOrConfig }}, len(elements))
228
+ entries := make([]*{{ $resourceSDKStructName }}, len(elements))
226
229
for idx, elt := range elements {
227
230
var list_diags diag.Diagnostics
228
- var entry *{{ .resourceSDKName }}.{{ .EntryOrConfig }}
231
+ var entry *{{ $resourceSDKStructName }}
229
232
entry, list_diags = elt.CopyToPango(ctx, nil)
230
233
resp.Diagnostics.Append(list_diags...)
231
234
if resp.Diagnostics.HasError() {
@@ -234,18 +237,141 @@ for idx, elt := range elements {
234
237
entries[idx] = entry
235
238
}
236
239
237
- objects := make([]{{ .structName }}{{ .ListAttribute.CamelCase }}Object, len(entries))
240
+ specifier, _, err := {{ .resourceSDKName }}.Versioning(r.client.Versioning())
241
+ if err != nil {
242
+ resp.Diagnostics.AddError("error while creating specifier", err.Error())
243
+ return
244
+ }
245
+
246
+ updates := xmlapi.NewMultiConfig(len(entries))
247
+
248
+ objects := make([]{{ $resourceTFStructName }}, len(entries))
249
+ for _, elt := range entries {
250
+ path, err := location.XpathWithEntryName(r.client.Versioning(), elt.Name)
251
+ if err != nil {
252
+ resp.Diagnostics.AddError("Failed to create xpath for existing entry", err.Error())
253
+ return
254
+ }
255
+
256
+ xmlEntry, err := specifier(*elt)
257
+ if err != nil {
258
+ resp.Diagnostics.AddError("Failed to transform Entry into XML document", err.Error())
259
+ return
260
+ }
261
+
262
+ updates.Add(&xmlapi.Config{
263
+ Action: "edit",
264
+ Xpath: util.AsXpath(path),
265
+ Element: xmlEntry,
266
+ Target: r.client.GetTarget(),
267
+ })
268
+ }
269
+
270
+ if len(updates.Operations) > 0 {
271
+ if _, _, _, err := r.client.MultiConfig(ctx, updates, false, nil); err != nil {
272
+ resp.Diagnostics.AddError("error updating entries", err.Error())
273
+ return
274
+ }
275
+ }
276
+
277
+ existing, err := svc.List(ctx, location, "get", "", "")
278
+ if err != nil && err.Error() != "Object not found" {
279
+ resp.Diagnostics.AddError("sdk error while listing resources", err.Error())
280
+ return
281
+ }
282
+
283
+ var movementRequired bool
284
+ {{- if .Exhaustive }}
285
+ // We manage the entire list of PAN-OS objects, so the order of entries
286
+ // from the plan is compared against all existing PAN-OS objects.
287
+ for idx, elt := range existing {
288
+ if processedStateEntries[elt.Name].StateIdx != idx {
289
+ movementRequired = true
290
+ }
291
+ processedStateEntries[elt.Name].Entry.Uuid = elt.Uuid
292
+ }
293
+ {{- else }}
294
+ // We only manage a subset of PAN-OS object on the given list, so care
295
+ // has to be taken to calculate the order of those managed elements on the
296
+ // PAN-OS side.
297
+ type entryWithState struct {
298
+ Entry *{{ $resourceSDKStructName }}
299
+ StateIdx int
300
+ }
301
+
302
+
303
+ plannedEntriesByName := make(map[string]*entryWithState, len(entries))
238
304
for idx, elt := range entries {
239
- created, err := svc.Create(ctx, location, *elt)
305
+ plannedEntriesByName[elt.Name] = &entryWithState{
306
+ Entry: elt,
307
+ StateIdx: idx,
308
+ }
309
+ }
310
+
311
+ // We filter all existing entries to end up with a list of entries that
312
+ // are in the plan. For every element of that list, we store its PAN-OS
313
+ // list index as StateIdx. Finally, the managedEntries index will serve
314
+ // as a way to check if managed entries are in order relative to each
315
+ // other.
316
+ managedEntries := make([]*entryWithState, len(entries))
317
+ for idx, elt := range existing {
318
+ if _, found := plannedEntriesByName[elt.Name]; found {
319
+ managedEntries = append(managedEntries, &entryWithState{
320
+ Entry: &elt,
321
+ StateIdx: idx,
322
+ })
323
+ }
324
+ }
325
+
326
+ var previousManagedEntry, previousPlannedEntry *entryWithState
327
+ for idx, elt := range managedEntries {
328
+ // plannedEntriesByName is a map of entries from the plan indexed by their
329
+ // name. If idx doesn't match StateIdx of the entry from the plan, the PAN-OS
330
+ // object is out of order.
331
+ plannedEntry := plannedEntriesByName[elt.Entry.Name]
332
+ if plannedEntry.StateIdx != idx {
333
+ movementRequired = true
334
+ break
335
+ }
336
+ // If this is the first element we are comparing, store it for future reference
337
+ // and continue. We will use it to calculate distance between two elements in
338
+ // PAN-OS list.
339
+ if previousManagedEntry == nil {
340
+ previousManagedEntry = elt
341
+ previousPlannedEntry = plannedEntry
342
+ continue
343
+ }
344
+
345
+ serverDistance := elt.StateIdx - previousManagedEntry.StateIdx
346
+ planDistance := plannedEntry.StateIdx - previousPlannedEntry.StateIdx
347
+
348
+ // If the distance between previous and current object differs between
349
+ // PAN-OS and the plan, we need to move objects around.
350
+ if serverDistance != planDistance {
351
+ movementRequired = true
352
+ break
353
+ }
354
+
355
+ previousManagedEntry = elt
356
+ previousPlannedEntry = plannedEntry
357
+ }
358
+ {{- end }}
359
+
360
+ if movementRequired {
361
+ entries := make([]{{ $resourceSDKStructName }}, len(plannedEntriesByName))
362
+ for _, elt := range plannedEntriesByName {
363
+ entries[elt.StateIdx] = *elt.Entry
364
+ }
365
+
366
+ trueValue := true
367
+ err = svc.MoveGroup(ctx, location, rule.Position{First: &trueValue}, entries)
240
368
if err != nil {
241
- resp.Diagnostics.AddError("SDK error during create ", err.Error())
369
+ resp.Diagnostics.AddError("Failed to reorder entries ", err.Error())
242
370
return
243
371
}
244
- var object {{ .structName }}{{ .ListAttribute.CamelCase }}Object
245
- object.CopyFromPango(ctx, created, nil)
246
- objects[idx] = object
247
372
}
248
373
374
+
249
375
var list_diags diag.Diagnostics
250
376
createdState.Location = state.Location
251
377
createdState.{{ .ListAttribute.CamelCase }}, list_diags = types.ListValueFrom(ctx, state.getTypeFor("{{ .ListAttribute.Underscore }}"), objects)
@@ -363,6 +489,17 @@ const resourceCreateFunction = `
363
489
`
364
490
365
491
const resourceReadManyFunction = `
492
+ {{- $structName := "" }}
493
+ {{- if eq .ResourceOrDS "DataSource" }}
494
+ {{ $structName = .dataSourceStructName }}
495
+ {{- else }}
496
+ {{ $structName = .resourceStructName }}
497
+ {{- end }}
498
+ {{- $resourceSDKStructName := printf "%s.%s" .resourceSDKName .EntryOrConfig }}
499
+ {{- $resourceTFStructName := printf "%s%sObject" $structName .ListAttribute.CamelCase }}
500
+ // {{ $resourceSDKStructName }}
501
+ // {{ $resourceTFStructName }}
502
+
366
503
{{- $stateName := "" }}
367
504
{{- if eq .ResourceOrDS "DataSource" }}
368
505
{{- $stateName = "Config" }}
@@ -393,19 +530,35 @@ if err != nil {
393
530
return
394
531
}
395
532
396
- {{- if not .Exhaustive }}
397
- // FIXME: For non-exhaustive variants (security_policy_rules etc.) we only want
398
- // to check if all entries are in place, and in the correct position.
399
- {{- end }}
400
-
401
-
533
+ {{- if .Exhaustive }}
534
+ // For resources that take sole ownership of a given list, Read()
535
+ // will return all existing entries from the server.
402
536
objects := make([]{{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object, len(existing))
403
537
for idx, elt := range existing {
404
- fmt.Printf("Read() entry: %v %v", elt.Name, *elt.Uuid)
405
538
var object {{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object
406
539
object.CopyFromPango(ctx, &elt, nil)
407
540
objects[idx] = object
408
541
}
542
+ {{- else }}
543
+ // For resources that only manage their own items in the list, Read()
544
+ // must only objects that are already part of the state.
545
+ var elements []{{ $resourceTFStructName }}
546
+ state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)
547
+ stateObjectsByName := make(map[string]*{{ $resourceTFStructName }}, len(elements))
548
+ for _, elt := range elements {
549
+ stateObjectsByName[elt.Name.ValueString()] = &elt
550
+ }
551
+
552
+ objects := make([]{{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object, len(state.{{ .ListAttribute.CamelCase }}.Elements()))
553
+ for idx, elt := range existing {
554
+ if _, found := stateObjectsByName[elt.Name]; !found {
555
+ continue
556
+ }
557
+ var object {{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object
558
+ object.CopyFromPango(ctx, &elt, nil)
559
+ objects[idx] = object
560
+ }
561
+ {{- end }}
409
562
410
563
411
564
var list_diags diag.Diagnostics
@@ -696,6 +849,16 @@ for _, existingElt := range existing {
696
849
resp.Diagnostics.AddError("Failed to create xpath for existing entry", err.Error())
697
850
}
698
851
852
+ _, foundInState := stateEntriesByName[existingElt.Name]
853
+ _, foundInRenamed := planEntriesByName[existingElt.Name]
854
+ _, foundInPlan := planEntriesByName[existingElt.Name]
855
+
856
+ if !foundInState && (foundInRenamed || foundInPlan) {
857
+ errorMsg := fmt.Sprintf("%s created outside of terraform", existingElt.Name)
858
+ resp.Diagnostics.AddError("Conflict between plan and PAN-OS data", errorMsg)
859
+ return
860
+ }
861
+
699
862
// If the existing entry name matches new name for the renamed entry,
700
863
// we delete it before adding Renamed commands.
701
864
if _, found := renamedEntries[existingElt.Name]; found {
@@ -707,6 +870,7 @@ for _, existingElt := range existing {
707
870
continue
708
871
}
709
872
873
+ {{- if .Exhaustive }}
710
874
processedElt, found := processedStateEntries[existingElt.Name]
711
875
if !found {
712
876
// If existing entry is not found in the processedEntries map, it's not
@@ -716,7 +880,10 @@ for _, existingElt := range existing {
716
880
Xpath: util.AsXpath(path),
717
881
Target: r.client.GetTarget(),
718
882
})
719
- } else if processedElt.Entry.Uuid != nil && *processedElt.Entry.Uuid == *existingElt.Uuid {
883
+ continue
884
+ }
885
+ {{- end }}
886
+ if processedElt.Entry.Uuid != nil && *processedElt.Entry.Uuid == *existingElt.Uuid {
720
887
// XXX: If entry from the plan is in process of being renamed, and its content
721
888
// differs from what exists on the server we should switch its state to entryOutdated
722
889
// instead.
0 commit comments