Skip to content

Commit 406495c

Browse files
kklimonda-clkklimonda
authored andcommitted
feat(codegen): Add resource identity support
1 parent b54ae98 commit 406495c

12 files changed

Lines changed: 266 additions & 5 deletions

File tree

pkg/naming/names.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ func Underscore(prefix, value, suffix string) string {
133133
return builder.String()
134134
}
135135

136+
func Dashed(prefix, value, suffix string) string {
137+
underscored := Underscore(prefix, value, suffix)
138+
return strings.ReplaceAll(underscored, "_", "-")
139+
}
140+
136141
// writeRuneAndApplyChangesForUnderscore contains logic to check all conditions for create under_score names and writes rune value.
137142
func writeRuneAndApplyChangesForUnderscore(runeValue int32, builder *strings.Builder, isFirstCharacter *bool) {
138143
if shouldResetFirstCharacterFlag(runeValue) {

pkg/properties/namevariant.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import (
99
type NameVariant struct {
1010
Original string
1111
Underscore string
12+
Dashed string
1213
CamelCase string
1314
LowerCamelCase string
1415
}
1516

1617
func NewNameVariant(name string) *NameVariant {
1718
return &NameVariant{
1819
Original: name,
20+
Dashed: naming.Dashed("", name, ""),
1921
Underscore: naming.Underscore("", name, ""),
2022
CamelCase: naming.CamelCase("", name, "", true),
2123
LowerCamelCase: naming.CamelCase("", name, "", false),

pkg/properties/provider_file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (o TerraformNameProvider) ActionStructName() string {
6464
return fmt.Sprintf("%sAction", o.StructName)
6565
}
6666

67+
func (o TerraformNameProvider) IdentityModelStructName() string {
68+
return fmt.Sprintf("%sIdentityModel", o.ResourceStructName)
69+
}
70+
6771
type TerraformSpecFlags uint
6872

6973
const (

pkg/translate/terraform_provider/entity_generators.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper
157157
terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango/movement", "")
158158
terraformProvider.ImportManager.AddStandardImport("strings", "")
159159
case properties.ResourceEntry:
160+
terraformProvider.ImportManager.AddStandardImport("strings", "")
161+
terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource/identityschema", "")
160162
case properties.ResourceEntryPlural:
161163
case properties.ResourceCustom, properties.ResourceConfig:
162164
}
@@ -215,7 +217,11 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper
215217
}
216218

217219
switch resourceTyp {
218-
case properties.ResourceEntry, properties.ResourceConfig:
220+
case properties.ResourceEntry:
221+
terraformProvider.ImportManager.AddStandardImport("strings", "")
222+
terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource/identityschema", "")
223+
terraformProvider.ImportManager.AddStandardImport("errors", "")
224+
case properties.ResourceConfig:
219225
terraformProvider.ImportManager.AddStandardImport("errors", "")
220226
case properties.ResourceEntryPlural, properties.ResourceUuid:
221227
case properties.ResourceUuidPlural, properties.ResourceCustom:
@@ -433,6 +439,8 @@ func (g *GenerateTerraformProvider) GenerateCommonCode(resourceTyp properties.Re
433439
names := NewNameProvider(spec, resourceTyp)
434440
funcMap := template.FuncMap{
435441
"HasLocations": func() bool { return len(spec.Locations) > 0 },
442+
"RenderIdentityModel": func() (string, error) { return RenderIdentityModel(resourceTyp, names, spec) },
443+
"RenderIdentitySchema": func() (string, error) { return RenderIdentitySchema(resourceTyp, names, spec) },
436444
"RenderLocationStructs": func() (string, error) { return RenderLocationStructs(resourceTyp, names, spec) },
437445
"RenderLocationSchemaGetter": func() (string, error) {
438446
return RenderLocationSchemaGetter(names, spec, terraformProvider.ImportManager)
@@ -441,6 +449,9 @@ func (g *GenerateTerraformProvider) GenerateCommonCode(resourceTyp properties.Re
441449
"RenderLocationAttributeTypes": func() (string, error) {
442450
return RenderLocationAttributeTypes(names, spec)
443451
},
452+
"RenderLocationAsIdentityGetter": func() (string, error) {
453+
return RenderLocationAsIdentityGetter(resourceTyp, names, spec)
454+
},
444455
}
445456
return g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaCommon, names, spec, terraformProvider, "common/common.tmpl", funcMap)
446457
}

pkg/translate/terraform_provider/location.go

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ type locationStructFieldCtx struct {
1212
Name *properties.NameVariant
1313
TerraformType string
1414
Type string
15+
AsIdentity bool
1516
Tags []string
1617
}
1718

1819
// locationStructCtx describes a location struct.
1920
type locationStructCtx struct {
2021
StructName string
22+
XpathName *properties.NameVariant
23+
TopLevel bool
2124
Fields []locationStructFieldCtx
2225
}
2326

@@ -32,6 +35,7 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
3235
// Create the top location structure that references other locations
3336
topLocation := locationStructCtx{
3437
StructName: fmt.Sprintf("%sLocation", names.StructName),
38+
TopLevel: true,
3539
}
3640

3741
for _, data := range spec.OrderedLocations() {
@@ -43,6 +47,7 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
4347
Name: data.Name,
4448
TerraformType: structName,
4549
Type: structType,
50+
AsIdentity: true,
4651
Tags: []string{tfTag},
4752
})
4853

@@ -64,13 +69,15 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
6469
paramTag = "`tfsdk:\"name\"`"
6570
}
6671
fields = append(fields, locationStructFieldCtx{
67-
Name: name,
68-
Type: "types.String",
69-
Tags: []string{paramTag},
72+
Name: name,
73+
Type: "types.String",
74+
AsIdentity: true,
75+
Tags: []string{paramTag},
7076
})
7177
}
7278

7379
location := locationStructCtx{
80+
XpathName: data.Name,
7481
StructName: structName,
7582
Fields: fields,
7683
}
@@ -390,3 +397,138 @@ func RenderLocationAttributeTypes(names *NameProvider, spec *properties.Normaliz
390397
}
391398
return processTemplate("location/attribute_types.tmpl", "render-location-structs", data, commonFuncMap)
392399
}
400+
401+
// RenderIdentityModel generates identity model struct.
402+
func RenderIdentityModel(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
403+
switch resourceTyp {
404+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
405+
return "", nil
406+
case properties.ResourceConfig, properties.ResourceCustom:
407+
return "", nil
408+
case properties.ResourceEntry:
409+
}
410+
411+
if spec.TerraformProviderConfig.Ephemeral {
412+
return "", nil
413+
}
414+
415+
if spec.TerraformProviderConfig.SkipResource {
416+
return "", nil
417+
}
418+
419+
type fieldCtx struct {
420+
Name string
421+
Type string
422+
Tag string
423+
}
424+
425+
type context struct {
426+
StructName string
427+
Fields []fieldCtx
428+
}
429+
430+
var fields []fieldCtx
431+
if spec.HasEntryName() {
432+
fields = []fieldCtx{
433+
{Name: "Name", Type: "types.String", Tag: "name"},
434+
{Name: "Location", Type: "types.String", Tag: "location"},
435+
}
436+
} else {
437+
fields = []fieldCtx{
438+
{Name: "Config", Type: "types.String", Tag: "config"},
439+
{Name: "Location", Type: "types.String", Tag: "location"},
440+
}
441+
}
442+
443+
data := context{
444+
StructName: names.IdentityModelStructName(),
445+
Fields: fields,
446+
}
447+
448+
return processTemplate("common/identity_model.tmpl", "render-identity-model", data, commonFuncMap)
449+
}
450+
451+
// RenderIdentitySchema generates identity schema method.
452+
func RenderIdentitySchema(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
453+
switch resourceTyp {
454+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
455+
return "", nil
456+
case properties.ResourceConfig, properties.ResourceCustom:
457+
return "", nil
458+
case properties.ResourceEntry:
459+
}
460+
461+
if spec.TerraformProviderConfig.Ephemeral {
462+
return "", nil
463+
}
464+
465+
if spec.TerraformProviderConfig.SkipResource {
466+
return "", nil
467+
}
468+
469+
type fieldCtx struct {
470+
Name string
471+
Type string
472+
RequiredForImport bool
473+
OptionalForImport bool
474+
}
475+
476+
type context struct {
477+
StructName string
478+
Fields []fieldCtx
479+
}
480+
481+
var fields []fieldCtx
482+
if spec.HasEntryName() {
483+
fields = []fieldCtx{
484+
{Name: "name", Type: "identityschema.StringAttribute", RequiredForImport: true},
485+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
486+
}
487+
} else {
488+
fields = []fieldCtx{
489+
{Name: "config", Type: "identityschema.StringAttribute", RequiredForImport: true},
490+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
491+
}
492+
}
493+
494+
data := context{
495+
StructName: fmt.Sprintf("%sResource", names.StructName),
496+
Fields: fields,
497+
}
498+
499+
return processTemplate("common/identity_schema.tmpl", "render-identity-schema", data, commonFuncMap)
500+
501+
}
502+
503+
// RenderLocationAsIdentityGetter generates location-to-identity conversion code.
504+
func RenderLocationAsIdentityGetter(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
505+
switch resourceTyp {
506+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
507+
return "", nil
508+
case properties.ResourceConfig, properties.ResourceCustom:
509+
return "", nil
510+
case properties.ResourceEntry:
511+
}
512+
513+
if spec.TerraformProviderConfig.Ephemeral {
514+
return "", nil
515+
}
516+
517+
if spec.TerraformProviderConfig.SkipResource {
518+
return "", nil
519+
}
520+
521+
type context struct {
522+
StructName string
523+
Specs []locationStructCtx
524+
}
525+
526+
locations := getLocationStructsContext(names, spec)
527+
528+
data := context{
529+
StructName: names.StructName,
530+
Specs: locations,
531+
}
532+
533+
return processTemplate("location/identity.tmpl", "render-location-as-identity", data, commonFuncMap)
534+
}

templates/terraform-provider/common/common.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
{{ RenderIdentityModel }}
2+
3+
{{ RenderIdentitySchema }}
14

25
{{- if HasLocations }}
36
{{- RenderLocationStructs }}
@@ -7,4 +10,6 @@
710
{{- RenderLocationMarshallers }}
811

912
{{- RenderLocationAttributeTypes }}
13+
14+
{{- RenderLocationAsIdentityGetter }}
1015
{{- end }}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
type {{ .StructName }} struct {
3+
{{- range .Fields }}
4+
{{ .Name }} {{ .Type }} `tfsdk:"{{ .Tag }}"`
5+
{{ end }}
6+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
func (o {{ .StructName }}) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
3+
resp.IdentitySchema = identityschema.Schema{
4+
Attributes: map[string]identityschema.Attribute{
5+
{{- range .Fields }}
6+
"{{ .Name }}": {{ .Type }}{
7+
{{- if .RequiredForImport }}
8+
RequiredForImport: true,
9+
{{- else if .OptionalForImport }}
10+
OptionalForImport: true,
11+
{{- end }}
12+
},
13+
{{- end }}
14+
},
15+
}
16+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
{{- define "topLevelLocation" }}
3+
{{- range $.Specs }}
4+
{{- if not .TopLevel }}{{- continue }}{{- end }}
5+
{{- range .Fields }}
6+
if !o.{{ .Name.CamelCase }}.IsNull() {
7+
var location {{ .TerraformType }}
8+
diags := o.{{ .Name.CamelCase }}.As(ctx, &location, basetypes.ObjectAsOptions{})
9+
if diags.HasError() {
10+
return types.StringNull(), diags
11+
}
12+
13+
identity, innerDiags := location.Identity(ctx)
14+
diags.Append(innerDiags...)
15+
if diags.HasError() {
16+
return types.StringNull(), diags
17+
}
18+
19+
formatted := fmt.Sprintf("/{{ .Name.Dashed }}%s", identity.ValueString())
20+
return types.StringValue(formatted), nil
21+
}
22+
{{- end }}
23+
{{- end }}
24+
return types.StringNull(), diag.Diagnostics{diag.NewAttributeErrorDiagnostic(
25+
path.Root("location"),
26+
"Location Attribute is Not Set",
27+
"The 'location' attribute must be configured with a non-empty value.",
28+
)}
29+
{{- end }}
30+
31+
{{- define "location" }}
32+
identity := []string{""}
33+
{{- range .Fields }}
34+
{{- if not .AsIdentity }}{{- continue }}{{- end }}
35+
{
36+
value := fmt.Sprintf("{{ .Name.Dashed }}[\"%s\"]", o.{{ .Name.CamelCase }}.ValueString())
37+
identity = append(identity, value)
38+
}
39+
{{- end }}
40+
return types.StringValue(strings.Join(identity, "/")), nil
41+
{{- end }}
42+
43+
{{- range .Specs }}
44+
func (o *{{ .StructName }}) Identity(ctx context.Context) (types.String, diag.Diagnostics) {
45+
{{- if .TopLevel }}
46+
{{- template "topLevelLocation" Map "Specs" $.Specs }}
47+
{{- else }}
48+
{{- template "location" . }}
49+
{{- end }}
50+
}
51+
{{- end }}

templates/terraform-provider/location/state_to_pango.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
{
32
var terraformLocation {{ .TerraformStructName }}
3+
{
44
resp.Diagnostics.Append({{ $.Source }}.As(ctx, &terraformLocation, basetypes.ObjectAsOptions{})...)
55
if resp.Diagnostics.HasError() {
66
return

0 commit comments

Comments
 (0)