Skip to content

Commit 008b671

Browse files
committed
feat(codegen): Add resource identity support
1 parent f9494f0 commit 008b671

6 files changed

Lines changed: 247 additions & 6 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/funcs.go

Lines changed: 194 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,112 @@ 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(names *NameProvider, spec *properties.Normalization) (string, error) {
3017+
if spec.TerraformProviderConfig.Ephemeral || spec.TerraformProviderConfig.ResourceType == properties.TerraformResourceCustom {
3018+
return "", nil
3019+
}
3020+
3021+
if spec.TerraformProviderConfig.SkipResource {
3022+
return "", nil
3023+
}
3024+
3025+
type fieldCtx struct {
3026+
Name string
3027+
Type string
3028+
Tag string
3029+
}
3030+
3031+
type context struct {
3032+
StructName string
3033+
Fields []fieldCtx
3034+
}
3035+
3036+
fields := []fieldCtx{
3037+
{Name: "Name", Type: "types.String", Tag: "name"},
3038+
{Name: "Location", Type: "types.String", Tag: "location"},
3039+
}
3040+
3041+
data := context{
3042+
StructName: fmt.Sprintf("%sResourceIdentityModel", names.StructName),
3043+
Fields: fields,
3044+
}
3045+
3046+
return processTemplate(identityModelTmpl, "render-identity-model", data, commonFuncMap)
3047+
}
3048+
3049+
const identitySchemaTmpl = `
3050+
func (o {{ .StructName }}) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
3051+
resp.IdentitySchema = identityschema.Schema{
3052+
Attributes: map[string]identityschema.Attribute{
3053+
{{- range .Fields }}
3054+
"{{ .Name }}": {{ .Type }}{
3055+
{{- if .RequiredForImport }}
3056+
RequiredForImport: true,
3057+
{{- else if .OptionalForImport }}
3058+
OptionalForImport: true,
3059+
{{- end }}
3060+
},
3061+
{{- end }}
3062+
},
3063+
}
3064+
}
3065+
`
3066+
3067+
func RenderIdentitySchema(names *NameProvider, spec *properties.Normalization) (string, error) {
3068+
if spec.TerraformProviderConfig.Ephemeral || spec.TerraformProviderConfig.ResourceType == properties.TerraformResourceCustom {
3069+
return "", nil
3070+
}
3071+
3072+
if spec.TerraformProviderConfig.SkipResource {
3073+
return "", nil
3074+
}
3075+
3076+
type fieldCtx struct {
3077+
Name string
3078+
Type string
3079+
RequiredForImport bool
3080+
OptionalForImport bool
3081+
}
3082+
3083+
type context struct {
3084+
StructName string
3085+
Fields []fieldCtx
3086+
}
3087+
3088+
fields := []fieldCtx{
3089+
{Name: "name", Type: "identityschema.StringAttribute", RequiredForImport: true},
3090+
{Name: "location", Type: "identityschema.StringAttribute", RequiredForImport: true},
3091+
}
3092+
3093+
data := context{
3094+
StructName: fmt.Sprintf("%sResource", names.StructName),
3095+
Fields: fields,
3096+
}
3097+
3098+
return processTemplate(identitySchemaTmpl, "render-identity-model", data, commonFuncMap)
3099+
3100+
}
3101+
3102+
const identityWriterTmpl = `
3103+
3104+
`
3105+
3106+
func RenderIdentityWriter(names *NameProvider, spec *properties.Normalization) (string, error) {
3107+
if spec.TerraformProviderConfig.Ephemeral {
3108+
return "", nil
3109+
}
3110+
3111+
return processTemplate(identitySchemaTmpl, "render-identity-model", nil, nil)
3112+
}
3113+
30013114
const attributeTypesTmpl = `
30023115
{{- range .Structs }}
30033116
func (o *{{ .StructName }}{{ .ModelOrObject }}) AttributeTypes() map[string]attr.Type {
@@ -3105,6 +3218,83 @@ func RenderLocationAttributeTypes(names *NameProvider, spec *properties.Normaliz
31053218
return processTemplate(locationAttributeTypesTmpl, "render-location-structs", data, commonFuncMap)
31063219
}
31073220

3221+
const locationAsIdentityTmpl = `
3222+
{{- define "topLevelLocation" }}
3223+
{{- range $.Specs }}
3224+
{{- if not .TopLevel }}{{- continue }}{{- end }}
3225+
{{- range .Fields }}
3226+
if !o.{{ .Name.CamelCase }}.IsNull() {
3227+
var location {{ .TerraformType }}
3228+
diags := o.{{ .Name.CamelCase }}.As(ctx, &location, basetypes.ObjectAsOptions{})
3229+
if diags.HasError() {
3230+
return types.StringNull(), diags
3231+
}
3232+
3233+
identity, innerDiags := location.Identity(ctx)
3234+
diags.Append(innerDiags...)
3235+
if diags.HasError() {
3236+
return types.StringNull(), diags
3237+
}
3238+
3239+
formatted := fmt.Sprintf("/{{ .Name.Dashed }}%s", identity.ValueString())
3240+
return types.StringValue(formatted), nil
3241+
}
3242+
{{- end }}
3243+
{{- end }}
3244+
return types.StringNull(), diag.Diagnostics{diag.NewAttributeErrorDiagnostic(
3245+
path.Root("location"),
3246+
"Location Attribute is Not Set",
3247+
"The 'location' attribute must be configured with a non-empty value.",
3248+
)}
3249+
{{- end }}
3250+
3251+
{{- define "location" }}
3252+
identity := []string{""}
3253+
{{- range .Fields }}
3254+
{{- if not .AsIdentity }}{{- continue }}{{- end }}
3255+
{
3256+
value := fmt.Sprintf("{{ .Name.Dashed }}=\"%s\"", o.{{ .Name.CamelCase }}.ValueString())
3257+
identity = append(identity, value)
3258+
}
3259+
{{- end }}
3260+
return types.StringValue(strings.Join(identity, "/")), nil
3261+
{{- end }}
3262+
3263+
{{- range .Specs }}
3264+
func (o *{{ .StructName }}) Identity(ctx context.Context) (types.String, diag.Diagnostics) {
3265+
{{- if .TopLevel }}
3266+
{{- template "topLevelLocation" Map "Specs" $.Specs }}
3267+
{{- else }}
3268+
{{- template "location" . }}
3269+
{{- end }}
3270+
}
3271+
{{- end }}
3272+
`
3273+
3274+
func RenderLocationAsIdentityGetter(names *NameProvider, spec *properties.Normalization) (string, error) {
3275+
if spec.TerraformProviderConfig.Ephemeral || spec.TerraformProviderConfig.ResourceType == properties.TerraformResourceCustom {
3276+
return "", nil
3277+
}
3278+
3279+
if spec.TerraformProviderConfig.SkipResource {
3280+
return "", nil
3281+
}
3282+
3283+
type context struct {
3284+
StructName string
3285+
Specs []locationStructCtx
3286+
}
3287+
3288+
locations := getLocationStructsContext(names, spec)
3289+
3290+
data := context{
3291+
StructName: names.StructName,
3292+
Specs: locations,
3293+
}
3294+
3295+
return processTemplate(locationAsIdentityTmpl, "render-location-as-identity", data, commonFuncMap)
3296+
}
3297+
31083298
const customTemplateForFunction = `
31093299
o.{{ .Function }}Custom(ctx, req, resp)
31103300
`

pkg/translate/terraform_provider/template.go

Lines changed: 23 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 not IsCustom }}
272+
_ resource.ResourceWithIdentity = &{{ resourceStructName }}{}
273+
{{- end }}
271274
)
272275
{{- end }}
273276
@@ -815,9 +818,22 @@ const resourceCreateFunction = `
815818
816819
{{ RenderEncryptedValuesFinalizer }}
817820
818-
// Done.
819821
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
820822
823+
identityLocation, diags := terraformLocation.Identity(ctx)
824+
resp.Diagnostics.Append(diags...)
825+
if resp.Diagnostics.HasError() {
826+
return
827+
}
828+
829+
identity := {{ .structName }}IdentityModel{
830+
{{- if .HasEntryName }}
831+
Name: state.Name,
832+
{{- end }}
833+
Location: identityLocation,
834+
}
835+
836+
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
821837
{{- /* Done */ -}}
822838
`
823839

@@ -2033,6 +2049,10 @@ const resourceImportStateFunctionTmpl = `
20332049
`
20342050

20352051
const commonTemplate = `
2052+
{{ RenderIdentityModel }}
2053+
2054+
{{ RenderIdentitySchema }}
2055+
20362056
{{- if HasLocations }}
20372057
{{- RenderLocationStructs }}
20382058
@@ -2041,6 +2061,8 @@ const commonTemplate = `
20412061
{{- RenderLocationMarshallers }}
20422062
20432063
{{- RenderLocationAttributeTypes }}
2064+
2065+
{{- RenderLocationAsIdentityGetter }}
20442066
{{- end }}
20452067
`
20462068

0 commit comments

Comments
 (0)