Skip to content

Commit 0725782

Browse files
committed
WIP: Adding support for Create() and Delete() of non-exhaustive lists
1 parent e042a69 commit 0725782

File tree

2 files changed

+193
-18
lines changed

2 files changed

+193
-18
lines changed

pkg/translate/terraform_provider/funcs.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1614,10 +1614,16 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv
16141614

16151615
var tmpl string
16161616
var listAttribute string
1617+
var exhaustive bool
16171618
switch resourceTyp {
16181619
case properties.ResourceEntry:
16191620
tmpl = resourceCreateFunction
1620-
default:
1621+
case properties.ResourceUuid:
1622+
exhaustive = true
1623+
tmpl = resourceCreateManyFunction
1624+
listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName)
1625+
case properties.ResourceUuidPlural:
1626+
exhaustive = false
16211627
tmpl = resourceCreateManyFunction
16221628
listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName)
16231629
}
@@ -1630,6 +1636,7 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv
16301636

16311637
data := map[string]interface{}{
16321638
"HasEncryptedResources": paramSpec.HasEncryptedResources(),
1639+
"Exhaustive": exhaustive,
16331640
"ListAttribute": listAttributeVariant,
16341641
"EntryOrConfig": paramSpec.EntryOrConfig(),
16351642
"HasEntryName": paramSpec.HasEntryName(),
@@ -1724,6 +1731,7 @@ func ResourceReadFunction(resourceTyp properties.ResourceType, names *NameProvid
17241731
"EntryOrConfig": paramSpec.EntryOrConfig(),
17251732
"HasEntryName": paramSpec.HasEntryName(),
17261733
"structName": names.StructName,
1734+
"datasourceStructName": names.DataSourceStructName,
17271735
"resourceStructName": names.ResourceStructName,
17281736
"serviceName": naming.CamelCase("", serviceName, "", false),
17291737
"resourceSDKName": resourceSDKName,

pkg/translate/terraform_provider/template.go

Lines changed: 184 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ func (r *{{ resourceStructName }}) ImportState(ctx context.Context, req resource
203203
{{- /* Done */ -}}`
204204

205205
const resourceCreateManyFunction = `
206+
{{ $resourceSDKStructName := printf "%s.%s" .resourceSDKName .EntryOrConfig }}
207+
{{ $resourceTFStructName := printf "%s%sObject" .structName .ListAttribute.CamelCase }}
208+
206209
var state, createdState {{ .structName }}Model
207210
resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...)
208211
if resp.Diagnostics.HasError() {
@@ -220,12 +223,12 @@ svc := {{ .resourceSDKName }}.NewService(r.client)
220223
var location {{ .resourceSDKName }}.Location
221224
{{ RenderLocationsStateToPango "state.Location" "location" }}
222225
223-
var elements []{{ .structName }}{{ .ListAttribute.CamelCase }}Object
226+
var elements []{{ $resourceTFStructName }}
224227
state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)
225-
entries := make([]*{{ .resourceSDKName }}.{{ .EntryOrConfig }}, len(elements))
228+
entries := make([]*{{ $resourceSDKStructName }}, len(elements))
226229
for idx, elt := range elements {
227230
var list_diags diag.Diagnostics
228-
var entry *{{ .resourceSDKName }}.{{ .EntryOrConfig }}
231+
var entry *{{ $resourceSDKStructName }}
229232
entry, list_diags = elt.CopyToPango(ctx, nil)
230233
resp.Diagnostics.Append(list_diags...)
231234
if resp.Diagnostics.HasError() {
@@ -234,18 +237,141 @@ for idx, elt := range elements {
234237
entries[idx] = entry
235238
}
236239
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))
238304
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)
240368
if err != nil {
241-
resp.Diagnostics.AddError("SDK error during create", err.Error())
369+
resp.Diagnostics.AddError("Failed to reorder entries", err.Error())
242370
return
243371
}
244-
var object {{ .structName }}{{ .ListAttribute.CamelCase }}Object
245-
object.CopyFromPango(ctx, created, nil)
246-
objects[idx] = object
247372
}
248373
374+
249375
var list_diags diag.Diagnostics
250376
createdState.Location = state.Location
251377
createdState.{{ .ListAttribute.CamelCase }}, list_diags = types.ListValueFrom(ctx, state.getTypeFor("{{ .ListAttribute.Underscore }}"), objects)
@@ -363,6 +489,17 @@ const resourceCreateFunction = `
363489
`
364490

365491
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+
366503
{{- $stateName := "" }}
367504
{{- if eq .ResourceOrDS "DataSource" }}
368505
{{- $stateName = "Config" }}
@@ -393,19 +530,35 @@ if err != nil {
393530
return
394531
}
395532
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.
402536
objects := make([]{{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object, len(existing))
403537
for idx, elt := range existing {
404-
fmt.Printf("Read() entry: %v %v", elt.Name, *elt.Uuid)
405538
var object {{ .structName }}{{ .ResourceOrDS }}{{ .ListAttribute.CamelCase }}Object
406539
object.CopyFromPango(ctx, &elt, nil)
407540
objects[idx] = object
408541
}
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 }}
409562
410563
411564
var list_diags diag.Diagnostics
@@ -696,6 +849,16 @@ for _, existingElt := range existing {
696849
resp.Diagnostics.AddError("Failed to create xpath for existing entry", err.Error())
697850
}
698851
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+
699862
// If the existing entry name matches new name for the renamed entry,
700863
// we delete it before adding Renamed commands.
701864
if _, found := renamedEntries[existingElt.Name]; found {
@@ -707,6 +870,7 @@ for _, existingElt := range existing {
707870
continue
708871
}
709872
873+
{{- if .Exhaustive }}
710874
processedElt, found := processedStateEntries[existingElt.Name]
711875
if !found {
712876
// If existing entry is not found in the processedEntries map, it's not
@@ -716,7 +880,10 @@ for _, existingElt := range existing {
716880
Xpath: util.AsXpath(path),
717881
Target: r.client.GetTarget(),
718882
})
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 {
720887
// XXX: If entry from the plan is in process of being renamed, and its content
721888
// differs from what exists on the server we should switch its state to entryOutdated
722889
// instead.

0 commit comments

Comments
 (0)