Skip to content

Commit caa895f

Browse files
committed
feat(codegen): Add resource identity support
1 parent b4a9653 commit caa895f

File tree

6 files changed

+278
-7
lines changed

6 files changed

+278
-7
lines changed

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/funcs.go

Lines changed: 222 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -964,11 +964,14 @@ type locationStructFieldCtx struct {
964964
Name *properties.NameVariant
965965
TerraformType string
966966
Type string
967+
AsIdentity bool
967968
Tags []string
968969
}
969970

970971
type locationStructCtx struct {
971972
StructName string
973+
XpathName *properties.NameVariant
974+
TopLevel bool
972975
Fields []locationStructFieldCtx
973976
}
974977

@@ -982,6 +985,7 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
982985
// Create the top location structure that references other locations
983986
topLocation := locationStructCtx{
984987
StructName: fmt.Sprintf("%sLocation", names.StructName),
988+
TopLevel: true,
985989
}
986990

987991
for _, data := range spec.OrderedLocations() {
@@ -993,6 +997,7 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
993997
Name: data.Name,
994998
TerraformType: structName,
995999
Type: structType,
1000+
AsIdentity: true,
9961001
Tags: []string{tfTag},
9971002
})
9981003

@@ -1022,13 +1027,15 @@ func getLocationStructsContext(names *NameProvider, spec *properties.Normalizati
10221027
paramTag = "`tfsdk:\"name\"`"
10231028
}
10241029
fields = append(fields, locationStructFieldCtx{
1025-
Name: name,
1026-
Type: "types.String",
1027-
Tags: []string{paramTag},
1030+
Name: name,
1031+
Type: "types.String",
1032+
AsIdentity: true,
1033+
Tags: []string{paramTag},
10281034
})
10291035
}
10301036

10311037
location := locationStructCtx{
1038+
XpathName: data.Name,
10321039
StructName: structName,
10331040
Fields: fields,
10341041
}
@@ -2483,8 +2490,8 @@ func RenderLocationsPangoToState(names *NameProvider, spec *properties.Normaliza
24832490
}
24842491

24852492
const locationsStateToPango = `
2486-
{
24872493
var terraformLocation {{ .TerraformStructName }}
2494+
{
24882495
resp.Diagnostics.Append({{ $.Source }}.As(ctx, &terraformLocation, basetypes.ObjectAsOptions{})...)
24892496
if resp.Diagnostics.HasError() {
24902497
return
@@ -2998,6 +3005,132 @@ func RenderStructs(resourceTyp properties.ResourceType, schemaTyp properties.Sch
29983005
return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap)
29993006
}
30003007

3008+
const identityModelTmpl = `
3009+
type {{ .StructName }} struct {
3010+
{{- range .Fields }}
3011+
{{ .Name }} {{ .Type }} ` + "`" + `tfsdk:"{{ .Tag }}"` + "`" + `
3012+
{{ end }}
3013+
}
3014+
`
3015+
3016+
func RenderIdentityModel(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
3017+
switch resourceTyp {
3018+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
3019+
return "", nil
3020+
case properties.ResourceConfig, properties.ResourceCustom:
3021+
return "", nil
3022+
case properties.ResourceEntry:
3023+
}
3024+
3025+
if spec.TerraformProviderConfig.Ephemeral {
3026+
return "", nil
3027+
}
3028+
3029+
if spec.TerraformProviderConfig.SkipResource {
3030+
return "", nil
3031+
}
3032+
3033+
type fieldCtx struct {
3034+
Name string
3035+
Type string
3036+
Tag string
3037+
}
3038+
3039+
type context struct {
3040+
StructName string
3041+
Fields []fieldCtx
3042+
}
3043+
3044+
var fields []fieldCtx
3045+
if spec.HasEntryName() {
3046+
fields = []fieldCtx{
3047+
{Name: "Name", Type: "types.String", Tag: "name"},
3048+
{Name: "Location", Type: "types.String", Tag: "location"},
3049+
}
3050+
} else {
3051+
fields = []fieldCtx{
3052+
{Name: "Config", Type: "types.String", Tag: "config"},
3053+
{Name: "Location", Type: "types.String", Tag: "location"},
3054+
}
3055+
}
3056+
3057+
data := context{
3058+
StructName: names.IdentityModelStructName(),
3059+
Fields: fields,
3060+
}
3061+
3062+
return processTemplate(identityModelTmpl, "render-identity-model", data, commonFuncMap)
3063+
}
3064+
3065+
const identitySchemaTmpl = `
3066+
func (o {{ .StructName }}) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
3067+
resp.IdentitySchema = identityschema.Schema{
3068+
Attributes: map[string]identityschema.Attribute{
3069+
{{- range .Fields }}
3070+
"{{ .Name }}": {{ .Type }}{
3071+
{{- if .RequiredForImport }}
3072+
RequiredForImport: true,
3073+
{{- else if .OptionalForImport }}
3074+
OptionalForImport: true,
3075+
{{- end }}
3076+
},
3077+
{{- end }}
3078+
},
3079+
}
3080+
}
3081+
`
3082+
3083+
func RenderIdentitySchema(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
3084+
switch resourceTyp {
3085+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
3086+
return "", nil
3087+
case properties.ResourceConfig, properties.ResourceCustom:
3088+
return "", nil
3089+
case properties.ResourceEntry:
3090+
}
3091+
3092+
if spec.TerraformProviderConfig.Ephemeral {
3093+
return "", nil
3094+
}
3095+
3096+
if spec.TerraformProviderConfig.SkipResource {
3097+
return "", nil
3098+
}
3099+
3100+
type fieldCtx struct {
3101+
Name string
3102+
Type string
3103+
RequiredForImport bool
3104+
OptionalForImport bool
3105+
}
3106+
3107+
type context struct {
3108+
StructName string
3109+
Fields []fieldCtx
3110+
}
3111+
3112+
var fields []fieldCtx
3113+
if spec.HasEntryName() {
3114+
fields = []fieldCtx{
3115+
{Name: "name", Type: "identityschema.StringAttribute", RequiredForImport: true},
3116+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
3117+
}
3118+
} else {
3119+
fields = []fieldCtx{
3120+
{Name: "config", Type: "identityschema.StringAttribute", RequiredForImport: true},
3121+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
3122+
}
3123+
}
3124+
3125+
data := context{
3126+
StructName: fmt.Sprintf("%sResource", names.StructName),
3127+
Fields: fields,
3128+
}
3129+
3130+
return processTemplate(identitySchemaTmpl, "render-identity-model", data, commonFuncMap)
3131+
3132+
}
3133+
30013134
const attributeTypesTmpl = `
30023135
{{- range .Structs }}
30033136
func (o *{{ .StructName }}{{ .ModelOrObject }}) AttributeTypes() map[string]attr.Type {
@@ -3101,6 +3234,91 @@ func RenderLocationAttributeTypes(names *NameProvider, spec *properties.Normaliz
31013234
return processTemplate(locationAttributeTypesTmpl, "render-location-structs", data, commonFuncMap)
31023235
}
31033236

3237+
const locationAsIdentityTmpl = `
3238+
{{- define "topLevelLocation" }}
3239+
{{- range $.Specs }}
3240+
{{- if not .TopLevel }}{{- continue }}{{- end }}
3241+
{{- range .Fields }}
3242+
if !o.{{ .Name.CamelCase }}.IsNull() {
3243+
var location {{ .TerraformType }}
3244+
diags := o.{{ .Name.CamelCase }}.As(ctx, &location, basetypes.ObjectAsOptions{})
3245+
if diags.HasError() {
3246+
return types.StringNull(), diags
3247+
}
3248+
3249+
identity, innerDiags := location.Identity(ctx)
3250+
diags.Append(innerDiags...)
3251+
if diags.HasError() {
3252+
return types.StringNull(), diags
3253+
}
3254+
3255+
formatted := fmt.Sprintf("/{{ .Name.Dashed }}%s", identity.ValueString())
3256+
return types.StringValue(formatted), nil
3257+
}
3258+
{{- end }}
3259+
{{- end }}
3260+
return types.StringNull(), diag.Diagnostics{diag.NewAttributeErrorDiagnostic(
3261+
path.Root("location"),
3262+
"Location Attribute is Not Set",
3263+
"The 'location' attribute must be configured with a non-empty value.",
3264+
)}
3265+
{{- end }}
3266+
3267+
{{- define "location" }}
3268+
identity := []string{""}
3269+
{{- range .Fields }}
3270+
{{- if not .AsIdentity }}{{- continue }}{{- end }}
3271+
{
3272+
value := fmt.Sprintf("{{ .Name.Dashed }}[\"%s\"]", o.{{ .Name.CamelCase }}.ValueString())
3273+
identity = append(identity, value)
3274+
}
3275+
{{- end }}
3276+
return types.StringValue(strings.Join(identity, "/")), nil
3277+
{{- end }}
3278+
3279+
{{- range .Specs }}
3280+
func (o *{{ .StructName }}) Identity(ctx context.Context) (types.String, diag.Diagnostics) {
3281+
{{- if .TopLevel }}
3282+
{{- template "topLevelLocation" Map "Specs" $.Specs }}
3283+
{{- else }}
3284+
{{- template "location" . }}
3285+
{{- end }}
3286+
}
3287+
{{- end }}
3288+
`
3289+
3290+
func RenderLocationAsIdentityGetter(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) {
3291+
switch resourceTyp {
3292+
case properties.ResourceUuid, properties.ResourceUuidPlural, properties.ResourceEntryPlural:
3293+
return "", nil
3294+
case properties.ResourceConfig, properties.ResourceCustom:
3295+
return "", nil
3296+
case properties.ResourceEntry:
3297+
}
3298+
3299+
if spec.TerraformProviderConfig.Ephemeral {
3300+
return "", nil
3301+
}
3302+
3303+
if spec.TerraformProviderConfig.SkipResource {
3304+
return "", nil
3305+
}
3306+
3307+
type context struct {
3308+
StructName string
3309+
Specs []locationStructCtx
3310+
}
3311+
3312+
locations := getLocationStructsContext(names, spec)
3313+
3314+
data := context{
3315+
StructName: names.StructName,
3316+
Specs: locations,
3317+
}
3318+
3319+
return processTemplate(locationAsIdentityTmpl, "render-location-as-identity", data, commonFuncMap)
3320+
}
3321+
31043322
const customTemplateForFunction = `
31053323
o.{{ .Function }}Custom(ctx, req, resp)
31063324
`

pkg/translate/terraform_provider/template.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ var (
268268
_ resource.Resource = &{{ resourceStructName }}{}
269269
_ resource.ResourceWithConfigure = &{{ resourceStructName }}{}
270270
_ resource.ResourceWithImportState = &{{ resourceStructName }}{}
271+
{{- if and (not IsCustom) (not IsResourcePlural) (not IsConfig) }}
272+
_ resource.ResourceWithIdentity = &{{ resourceStructName }}{}
273+
{{- end }}
271274
)
272275
{{- end }}
273276
@@ -815,9 +818,24 @@ const resourceCreateFunction = `
815818
816819
{{ RenderEncryptedValuesFinalizer }}
817820
818-
// Done.
819821
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
820822
823+
{{- if .HasEntryName }}
824+
identityLocation, diags := terraformLocation.Identity(ctx)
825+
resp.Diagnostics.Append(diags...)
826+
if resp.Diagnostics.HasError() {
827+
return
828+
}
829+
830+
identity := {{ .structName }}IdentityModel{
831+
{{- if .HasEntryName }}
832+
Name: state.Name,
833+
{{- end }}
834+
Location: identityLocation,
835+
}
836+
837+
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
838+
{{- end }}
821839
{{- /* Done */ -}}
822840
`
823841

@@ -2033,6 +2051,10 @@ const resourceImportStateFunctionTmpl = `
20332051
`
20342052

20352053
const commonTemplate = `
2054+
{{ RenderIdentityModel }}
2055+
2056+
{{ RenderIdentitySchema }}
2057+
20362058
{{- if HasLocations }}
20372059
{{- RenderLocationStructs }}
20382060
@@ -2041,6 +2063,8 @@ const commonTemplate = `
20412063
{{- RenderLocationMarshallers }}
20422064
20432065
{{- RenderLocationAttributeTypes }}
2066+
2067+
{{- RenderLocationAsIdentityGetter }}
20442068
{{- end }}
20452069
`
20462070

0 commit comments

Comments
 (0)