Skip to content

Commit d00b54d

Browse files
committed
feat(codegen): Add resource identity support
1 parent 75e0e57 commit d00b54d

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

@@ -72,13 +77,15 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
7277
paramTag = "`tfsdk:\"name\"`"
7378
}
7479
fields = append(fields, locationStructFieldCtx{
75-
Name: name,
76-
Type: "types.String",
77-
Tags: []string{paramTag},
80+
Name: name,
81+
Type: "types.String",
82+
AsIdentity: true,
83+
Tags: []string{paramTag},
7884
})
7985
}
8086

8187
location := locationStructCtx{
88+
XpathName: data.Name,
8289
StructName: structName,
8390
Fields: fields,
8491
}
@@ -416,3 +423,138 @@ func RenderLocationAttributeTypes(names *NameProvider, spec *properties.Normaliz
416423
}
417424
return processTemplate("location/attribute_types.tmpl", "render-location-structs", data, commonFuncMap)
418425
}
426+
427+
// RenderIdentityModel generates identity model struct.
428+
func RenderIdentityModel(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
429+
switch resourceTyp {
430+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
431+
return "", nil
432+
case properties.ResourceConfig, properties.ResourceCustom:
433+
return "", nil
434+
case properties.ResourceEntry:
435+
}
436+
437+
if spec.TerraformProviderConfig.Ephemeral {
438+
return "", nil
439+
}
440+
441+
if spec.TerraformProviderConfig.SkipResource {
442+
return "", nil
443+
}
444+
445+
type fieldCtx struct {
446+
Name string
447+
Type string
448+
Tag string
449+
}
450+
451+
type context struct {
452+
StructName string
453+
Fields []fieldCtx
454+
}
455+
456+
var fields []fieldCtx
457+
if spec.HasEntryName() {
458+
fields = []fieldCtx{
459+
{Name: "Name", Type: "types.String", Tag: "name"},
460+
{Name: "Location", Type: "types.String", Tag: "location"},
461+
}
462+
} else {
463+
fields = []fieldCtx{
464+
{Name: "Config", Type: "types.String", Tag: "config"},
465+
{Name: "Location", Type: "types.String", Tag: "location"},
466+
}
467+
}
468+
469+
data := context{
470+
StructName: names.IdentityModelStructName(),
471+
Fields: fields,
472+
}
473+
474+
return processTemplate("common/identity_model.tmpl", "render-identity-model", data, commonFuncMap)
475+
}
476+
477+
// RenderIdentitySchema generates identity schema method.
478+
func RenderIdentitySchema(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
479+
switch resourceTyp {
480+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
481+
return "", nil
482+
case properties.ResourceConfig, properties.ResourceCustom:
483+
return "", nil
484+
case properties.ResourceEntry:
485+
}
486+
487+
if spec.TerraformProviderConfig.Ephemeral {
488+
return "", nil
489+
}
490+
491+
if spec.TerraformProviderConfig.SkipResource {
492+
return "", nil
493+
}
494+
495+
type fieldCtx struct {
496+
Name string
497+
Type string
498+
RequiredForImport bool
499+
OptionalForImport bool
500+
}
501+
502+
type context struct {
503+
StructName string
504+
Fields []fieldCtx
505+
}
506+
507+
var fields []fieldCtx
508+
if spec.HasEntryName() {
509+
fields = []fieldCtx{
510+
{Name: "name", Type: "identityschema.StringAttribute", RequiredForImport: true},
511+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
512+
}
513+
} else {
514+
fields = []fieldCtx{
515+
{Name: "config", Type: "identityschema.StringAttribute", RequiredForImport: true},
516+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
517+
}
518+
}
519+
520+
data := context{
521+
StructName: fmt.Sprintf("%sResource", names.StructName),
522+
Fields: fields,
523+
}
524+
525+
return processTemplate("common/identity_schema.tmpl", "render-identity-schema", data, commonFuncMap)
526+
527+
}
528+
529+
// RenderLocationAsIdentityGetter generates location-to-identity conversion code.
530+
func RenderLocationAsIdentityGetter(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
531+
switch resourceTyp {
532+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
533+
return "", nil
534+
case properties.ResourceConfig, properties.ResourceCustom:
535+
return "", nil
536+
case properties.ResourceEntry:
537+
}
538+
539+
if spec.TerraformProviderConfig.Ephemeral {
540+
return "", nil
541+
}
542+
543+
if spec.TerraformProviderConfig.SkipResource {
544+
return "", nil
545+
}
546+
547+
type context struct {
548+
StructName string
549+
Specs []locationStructCtx
550+
}
551+
552+
locations := getLocationStructsContext(names, spec)
553+
554+
data := context{
555+
StructName: names.StructName,
556+
Specs: locations,
557+
}
558+
559+
return processTemplate("location/identity.tmpl", "render-location-as-identity", data, commonFuncMap)
560+
}

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)