Skip to content

Commit 5a94e1d

Browse files
committed
feat(codegen): Add support for terraform lists/queries
1 parent d00b54d commit 5a94e1d

36 files changed

Lines changed: 552 additions & 96 deletions

assets/terraform/internal/manager/entry_import.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type SDKImportableEntryService[E EntryObject, L EntryLocation, IL ImportLocation
1818
Delete(context.Context, L, []IL, ...string) error
1919
ImportToLocations(context.Context, L, []IL, string) error
2020
UnimportFromLocations(context.Context, L, []IL, []string) error
21+
ListWithXpath(context.Context, string, string, string, string) ([]E, error)
2122
}
2223

2324
type ImportableEntryObjectManager[E EntryObject, L EntryLocation, IL ImportLocation, IS SDKImportableEntryService[E, L, IL]] struct {
@@ -38,8 +39,22 @@ func NewImportableEntryObjectManager[E EntryObject, L EntryLocation, IL ImportLo
3839
}
3940
}
4041

41-
func (o *ImportableEntryObjectManager[E, L, IL, IS]) ReadMany(ctx context.Context, location L, entries []E) ([]E, error) {
42-
return nil, &Error{err: ErrInternal, message: "called ReadMany on an importable singular resource"}
42+
func (o *ImportableEntryObjectManager[E, L, IL, IS]) ReadMany(ctx context.Context, location L, components []string) ([]E, error) {
43+
xpath, err := location.XpathWithComponents(o.client.Versioning(), append(components, util.AsEntryXpath(""))...)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
existing, err := o.service.ListWithXpath(ctx, util.AsXpath(xpath), "get", "", "")
49+
if err != nil {
50+
if sdkerrors.IsObjectNotFound(err) {
51+
return nil, ErrObjectNotFound
52+
} else {
53+
return nil, &Error{err: err, message: "Failed to read entries from the server"}
54+
}
55+
}
56+
57+
return existing, nil
4358
}
4459

4560
func (o *ImportableEntryObjectManager[E, L, IL, IS]) Read(ctx context.Context, location L, components []string, name string) (E, error) {

pkg/commands/codegen/codegen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func (c *Command) Execute() error {
7676
var dataSourceList []string
7777
var ephemeralResourceList []string
7878
var actionsList []string
79+
var listResourceList []string
7980
specMetadata := make(map[string]properties.TerraformProviderSpecMetadata)
8081

8182
for _, specPath := range c.specs {
@@ -138,6 +139,7 @@ func (c *Command) Execute() error {
138139
dataSourceList = append(dataSourceList, data.DataSources...)
139140
ephemeralResourceList = append(ephemeralResourceList, data.EphemeralResources...)
140141
actionsList = append(actionsList, data.Actions...)
142+
listResourceList = append(listResourceList, data.ListResources...)
141143

142144
for k, v := range data.SpecMetadata {
143145
specMetadata[k] = v
@@ -168,6 +170,7 @@ func (c *Command) Execute() error {
168170
dataSourceList = append(dataSourceList, data.DataSources...)
169171
ephemeralResourceList = append(ephemeralResourceList, data.EphemeralResources...)
170172
actionsList = append(actionsList, data.Actions...)
173+
listResourceList = append(listResourceList, data.ListResources...)
171174

172175
for k, v := range data.SpecMetadata {
173176
specMetadata[k] = v
@@ -190,6 +193,7 @@ func (c *Command) Execute() error {
190193
newProviderObject.DataSources = append(newProviderObject.DataSources, dataSourceList...)
191194
newProviderObject.Resources = append(newProviderObject.Resources, resourceList...)
192195
newProviderObject.EphemeralResources = append(newProviderObject.EphemeralResources, ephemeralResourceList...)
196+
newProviderObject.ListResources = append(newProviderObject.ListResources, listResourceList...)
193197
newProviderObject.Actions = append(newProviderObject.Actions, actionsList...)
194198
newProviderObject.SpecMetadata = specMetadata
195199

pkg/generate/generator.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (c *Creator) RenderTemplate() error {
6060

6161
type TerraformProviderFileData struct {
6262
EphemeralResources []string
63+
ListResources []string
6364
Resources []string
6465
DataSources []string
6566
Actions []string
@@ -90,6 +91,12 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty
9091
return nil, err
9192
}
9293

94+
if !*spec.TerraformProviderConfig.SkipListResource && (typ == properties.ResourceEntry || typ == properties.ResourceUuid) {
95+
if err := tfp.GenerateTerraformListResource(typ, spec, terraformProvider); err != nil {
96+
return nil, err
97+
}
98+
}
99+
93100
if spec.TerraformProviderConfig.Action {
94101
if err := tfp.GenerateTerraformAction(spec, terraformProvider); err != nil {
95102
return nil, err
@@ -126,6 +133,7 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty
126133
EphemeralResources: terraformProvider.EphemeralResources,
127134
Actions: terraformProvider.Actions,
128135
SpecMetadata: terraformProvider.SpecMetadata,
136+
ListResources: terraformProvider.ListResources,
129137
}
130138
return data, nil
131139
}

pkg/properties/normalized.go

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"io/fs"
77
"maps"
8-
"os"
98
"path/filepath"
109
"regexp"
1110
"runtime"
@@ -85,6 +84,7 @@ type TerraformProviderConfig struct {
8584
Action bool `json:"action" yaml:"action"`
8685
CustomValidation bool `json:"custom_validation" yaml:"custom_validation"`
8786
SkipResource bool `json:"skip_resource" yaml:"skip_resource"`
87+
SkipListResource *bool `json:"skip_list_resource", yaml:"skip_list_resource"`
8888
SkipDatasource bool `json:"skip_datasource" yaml:"skip_datasource"`
8989
SkipDatasourceListing bool `json:"skip_datasource_listing" yaml:"skip_datasource_listing"`
9090
ResourceType TerraformResourceType `json:"resource_type" yaml:"resource_type"`
@@ -722,6 +722,7 @@ func schemaToSpec(object object.Object) (*Normalization, error) {
722722
Action: object.TerraformConfig.Action,
723723
CustomValidation: object.TerraformConfig.CustomValidation,
724724
SkipResource: object.TerraformConfig.SkipResource,
725+
SkipListResource: object.TerraformConfig.SkipListResource,
725726
SkipDatasource: object.TerraformConfig.SkipDatasource,
726727
SkipDatasourceListing: object.TerraformConfig.SkipdatasourceListing,
727728
ResourceType: TerraformResourceType(object.TerraformConfig.ResourceType),
@@ -1126,26 +1127,20 @@ func (spec *Normalization) ResourceXpathVariablesCount() int {
11261127
return len(spec.PanosXpath.Variables)
11271128
}
11281129

1129-
// loadTemplate loads a template from either sdk or terraform-provider templates directory
1130-
func loadTemplate(templateType string, templatePath string) (string, error) {
1131-
fullPath := filepath.Join("templates", templateType, templatePath)
1132-
content, err := os.ReadFile(fullPath)
1133-
if err != nil {
1134-
// Try from parent directories (for when running from subdirectories)
1135-
for i := 1; i <= 3; i++ {
1136-
prefix := strings.Repeat("../", i)
1137-
altPath := filepath.Join(prefix, "templates", templateType, templatePath)
1138-
content, err = os.ReadFile(altPath)
1139-
if err == nil {
1140-
break
1141-
}
1142-
}
1143-
if err != nil {
1144-
return "", fmt.Errorf("failed to read template %s: %w", fullPath, err)
1145-
}
1146-
}
1147-
return string(content), nil
1130+
const attributesFromXpathComponentsTmpl = `
1131+
{{- range .Components }}
1132+
{{ if eq .Type "value" }}
1133+
{{ $.Target }}.{{ .Name }} = types.StringValue(components[{{ .Idx }}])
1134+
{{- else if eq .Type "entry" }}
1135+
{
1136+
component := components[{{ .Idx }}]
1137+
component = strings.TrimPrefix(component, "entry[@name='")
1138+
component = strings.TrimSuffix(component, "']")
1139+
{{ $.Target }}.{{ .Name.CamelCase }} = types.StringValue(component)
11481140
}
1141+
{{- end }}
1142+
{{- end }}
1143+
`
11491144

11501145
func (spec *Normalization) AttributesFromXpathComponents(target string) (string, error) {
11511146
type componentCtx struct {
@@ -1182,43 +1177,66 @@ func (spec *Normalization) AttributesFromXpathComponents(target string) (string,
11821177
Components: components,
11831178
}
11841179

1185-
tmplContent, err := loadTemplate("terraform-provider", "partials/attributes_from_xpath_components.tmpl")
1186-
if err != nil {
1187-
return "", err
1188-
}
1189-
1190-
tmpl := template.Must(template.New("attributes-from-xpath-components").Parse(tmplContent))
1180+
tmpl := template.Must(template.New("attributes-from-xpath-components").Parse(attributesFromXpathComponentsTmpl))
11911181

11921182
var buf bytes.Buffer
1193-
err = tmpl.Execute(&buf, data)
1183+
err := tmpl.Execute(&buf, data)
11941184
if err != nil {
11951185
panic(err)
11961186
}
11971187

11981188
return buf.String(), nil
11991189
}
12001190

1191+
const resourceXpathAssignmentsTmpl = `
1192+
{{- range .Variables }}
1193+
{{- if or (eq .Type "entry") (eq .Type "value") }}
1194+
ans = append(ans, components[{{ .Idx }}])
1195+
{{- else if eq .Type "static" }}
1196+
ans = append(ans, "{{ .Name.Original }}")
1197+
{{- end }}
1198+
{{- end }}
1199+
`
1200+
12011201
func (spec *Normalization) ResourceXpathAssignments() (string, error) {
12021202
data := xpathVariablesCtx{
12031203
Variables: createXpathVariablesContext(spec),
12041204
}
12051205

1206-
tmplContent, err := loadTemplate("sdk", "partials/resource_xpath_assignments.tmpl")
1207-
if err != nil {
1208-
return "", err
1209-
}
1210-
1211-
tmpl := template.Must(template.New("resource-xpath-assignments").Parse(tmplContent))
1206+
tmpl := template.Must(template.New("resource-xpath-assignments").Parse(resourceXpathAssignmentsTmpl))
12121207

12131208
var buf bytes.Buffer
1214-
err = tmpl.Execute(&buf, data)
1209+
err := tmpl.Execute(&buf, data)
12151210
if err != nil {
12161211
panic(err)
12171212
}
12181213

12191214
return buf.String(), nil
12201215
}
12211216

1217+
const xpathVariableChecksTmpl = `
1218+
{{- range .Variables }}
1219+
{{- if eq .Type "entry" }}
1220+
{
1221+
component := components[{{ .Idx }}]
1222+
{{- if .AllowEmpty }}
1223+
if component != "entry" {
1224+
{{- end }}
1225+
if !strings.HasPrefix(component, "entry[@name=\"]") && !strings.HasPrefix(component, "entry[@name='") {
1226+
return nil, errors.NewInvalidXpathComponentError(fmt.Sprintf("{{ .Name.CamelCase }} must be formatted as entry: %s", component))
1227+
}
1228+
1229+
if !strings.HasSuffix(component, "\"]") && !strings.HasSuffix(component, "']") {
1230+
return nil, errors.NewInvalidXpathComponentError(fmt.Sprintf("{{ .Name.CamelCase }} must be formatted as entry: %s", component))
1231+
}
1232+
{{- if .AllowEmpty }}
1233+
}
1234+
{{- end }}
1235+
}
1236+
{{- end }}
1237+
{{- end }}
1238+
`
1239+
12221240
type xpathVariableCtx struct {
12231241
Type object.PanosXpathVariableType
12241242
Name *NameVariant
@@ -1283,15 +1301,10 @@ func (spec *Normalization) ResourceXpathVariableChecks() (string, error) {
12831301
Variables: createXpathVariablesContext(spec),
12841302
}
12851303

1286-
tmplContent, err := loadTemplate("sdk", "partials/xpath_variable_checks.tmpl")
1287-
if err != nil {
1288-
return "", err
1289-
}
1290-
12911304
var buf bytes.Buffer
1292-
tmpl := template.Must(template.New("xpath-variable-checks").Parse(tmplContent))
1305+
tmpl := template.Must(template.New("xpath-variable-checks").Parse(xpathVariableChecksTmpl))
12931306

1294-
err = tmpl.Execute(&buf, data)
1307+
err := tmpl.Execute(&buf, data)
12951308
if err != nil {
12961309
panic(err)
12971310
}

pkg/properties/provider_file.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func (o TerraformNameProvider) IdentityModelStructName() string {
6868
return fmt.Sprintf("%sIdentityModel", o.ResourceStructName)
6969
}
7070

71+
func (o TerraformNameProvider) ListResourceStructName() string {
72+
return fmt.Sprintf("%sListResource", o.StructName)
73+
}
74+
7175
type TerraformSpecFlags uint
7276

7377
const (
@@ -91,6 +95,7 @@ type TerraformProviderFile struct {
9195
DataSources []string
9296
Resources []string
9397
EphemeralResources []string
98+
ListResources []string
9499
Actions []string
95100
SpecMetadata map[string]TerraformProviderSpecMetadata
96101
Code *strings.Builder

pkg/properties/resourcetype.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ type SchemaType string
1616
const (
1717
SchemaResource SchemaType = "resource"
1818
SchemaEphemeralResource SchemaType = "ephemeral-resource"
19+
SchemaListResource SchemaType = "list-resource"
1920
SchemaAction SchemaType = "action"
2021
SchemaDataSource SchemaType = "datasource"
2122
SchemaCommon SchemaType = "common"
2223
SchemaProvider SchemaType = "provider"
24+
SchemaCustom SchemaType = "custom"
2325
)

pkg/schema/object/object.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type TerraformConfig struct {
4343
SkipResource bool `yaml:"skip_resource"`
4444
SkipDatasource bool `yaml:"skip_datasource"`
4545
SkipdatasourceListing bool `yaml:"skip_datasource_listing"`
46+
SkipListResource *bool `yaml:"skip_list_resource"`
4647
ResourceType TerraformResourceType `yaml:"resource_type"`
4748
XmlNode *string `yaml:"xml_node"`
4849
CustomFunctions map[string]bool `yaml:"custom_functions"`
@@ -167,6 +168,11 @@ func (o *Object) UnmarshalYAML(n *yaml.Node) error {
167168
return fmt.Errorf("failed to unmarshal yaml spec: plural_type must be list for uuid resource types")
168169
}
169170

171+
if obj.TerraformConfig.SkipListResource == nil {
172+
skipListResource := true
173+
obj.TerraformConfig.SkipListResource = &skipListResource
174+
}
175+
170176
return nil
171177
}
172178

pkg/translate/terraform_provider/crud_operations.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv
3535
return RendeCreateUpdateMovementRequired(state, entries)
3636
},
3737
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
38-
return RenderLocationsStateToPango(names, paramSpec, source, dest)
38+
return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics")
3939
},
4040
}
4141

@@ -151,7 +151,7 @@ func DataSourceReadFunction(resourceTyp properties.ResourceType, names *NameProv
151151
return RenderLocationsPangoToState(names, paramSpec, source, dest)
152152
},
153153
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
154-
return RenderLocationsStateToPango(names, paramSpec, source, dest)
154+
return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics")
155155
},
156156
}
157157

@@ -219,7 +219,7 @@ func ResourceReadFunction(resourceTyp properties.ResourceType, names *NameProvid
219219
return RenderLocationsPangoToState(names, paramSpec, source, dest)
220220
},
221221
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
222-
return RenderLocationsStateToPango(names, paramSpec, source, dest)
222+
return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics")
223223
},
224224
}
225225

@@ -281,7 +281,7 @@ func ResourceUpdateFunction(resourceTyp properties.ResourceType, names *NameProv
281281
return RendeCreateUpdateMovementRequired(state, entries)
282282
},
283283
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
284-
return RenderLocationsStateToPango(names, paramSpec, source, dest)
284+
return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics")
285285
},
286286
"RenderLocationsPangoToState": func(source string, dest string) (string, error) {
287287
return RenderLocationsPangoToState(names, paramSpec, source, dest)
@@ -345,7 +345,7 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv
345345
return RenderImportLocationAssignment(names, paramSpec, source, dest)
346346
},
347347
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
348-
return RenderLocationsStateToPango(names, paramSpec, source, dest)
348+
return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics")
349349
},
350350
}
351351

0 commit comments

Comments
 (0)