Skip to content

Commit 3601cf7

Browse files
committed
Added generation of location-related code across all templates
1 parent 5626dc9 commit 3601cf7

File tree

6 files changed

+429
-156
lines changed

6 files changed

+429
-156
lines changed

pkg/translate/terraform_provider/funcs.go

Lines changed: 322 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,310 @@ func RenderCopyFromPangoFunctions(pkgName string, terraformTypePrefix string, pr
375375
return processTemplate(copyFromPangoTmpl, "copy-from-pango", data, funcMap)
376376
}
377377

378-
func ResourceCreateFunction(structName string, serviceName string, paramSpec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceSDKName string) (string, error) {
378+
const renderLocationTmpl = `
379+
{{- range .Locations }}
380+
type {{ .StructName }} struct {
381+
{{- range .Fields }}
382+
{{ .Name }} {{ .Type }} {{ range .Tags }}{{ . }} {{ end }}
383+
{{- end }}
384+
}
385+
{{- end }}
386+
`
387+
388+
func RenderLocationStructs(names *NameProvider, spec *properties.Normalization) (string, error) {
389+
type fieldCtx struct {
390+
Name string
391+
Type string
392+
Tags []string
393+
}
394+
395+
type locationCtx struct {
396+
StructName string
397+
Fields []fieldCtx
398+
}
399+
400+
type context struct {
401+
Locations []locationCtx
402+
}
403+
404+
var locations []locationCtx
405+
406+
// Create the top location structure that references other locations
407+
topLocation := locationCtx{
408+
StructName: fmt.Sprintf("%sLocation", names.StructName),
409+
}
410+
411+
for name, data := range spec.Locations {
412+
structName := fmt.Sprintf("%s%sLocation", names.StructName, pascalCase(name))
413+
tfTag := fmt.Sprintf("`tfsdk:\"%s\"`", name)
414+
var structType string
415+
if len(data.Vars) > 0 {
416+
structType = fmt.Sprintf("*%s", structName)
417+
} else {
418+
structType = "types.Bool"
419+
}
420+
421+
topLocation.Fields = append(topLocation.Fields, fieldCtx{
422+
Name: pascalCase(name),
423+
Type: structType,
424+
Tags: []string{tfTag},
425+
})
426+
427+
if len(data.Vars) == 0 {
428+
continue
429+
}
430+
431+
var fields []fieldCtx
432+
for paramName, param := range data.Vars {
433+
paramTag := fmt.Sprintf("`tfsdk:\"%s\"`", paramName)
434+
fields = append(fields, fieldCtx{
435+
Name: param.Name.CamelCase,
436+
Type: "types.String",
437+
Tags: []string{paramTag},
438+
})
439+
}
440+
441+
location := locationCtx{
442+
StructName: structName,
443+
Fields: fields,
444+
}
445+
locations = append(locations, location)
446+
}
447+
448+
locations = append(locations, topLocation)
449+
450+
data := context{
451+
Locations: locations,
452+
}
453+
return processTemplate(renderLocationTmpl, "render-location-structs", data, commonFuncMap)
454+
}
455+
456+
const locationSchemaGetterTmpl = `
457+
{{- define "renderLocationAttribute" }}
458+
"{{ .Name }}": {{ .SchemaType }}{
459+
Description: "{{ .Description }}",
460+
{{- if .Required }}
461+
Required: true
462+
{{- else }}
463+
Optional: true,
464+
{{- end }}
465+
{{- if .Computed }}
466+
Computed: true,
467+
{{- end }}
468+
{{- if .Default }}
469+
Default: {{ .Default.Type }}({{ .Default.Value }}),
470+
{{- end }}
471+
{{- if .Attributes }}
472+
Attributes: map[string]rsschema.Attribute{
473+
{{- range .Attributes }}
474+
{{- template "renderLocationAttribute" . }}
475+
{{- end }}
476+
},
477+
{{- end }}
478+
PlanModifiers: []planmodifier.{{ .ModifierType }}{
479+
{{ .ModifierType | LowerCase }}planmodifier.RequiresReplace(),
480+
},
481+
},
482+
{{- end }}
483+
484+
func {{ .StructName }}LocationsSchema() rsschema.Attribute {
485+
{{- with .Schema }}
486+
return rsschema.SingleNestedAttribute{
487+
Description: "{{ .Description }}",
488+
Required: true,
489+
Attributes: map[string]rsschema.Attribute{
490+
{{- range .Attributes }}
491+
{{- template "renderLocationAttribute" . }}
492+
{{- end }}
493+
},
494+
}
495+
}
496+
{{- end }}
497+
`
498+
499+
func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalization) (string, error) {
500+
type defaultCtx struct {
501+
Type string
502+
Value string
503+
}
504+
505+
type attributeCtx struct {
506+
Name string
507+
SchemaType string
508+
Description string
509+
Required bool
510+
Computed bool
511+
Default *defaultCtx
512+
ModifierType string
513+
Attributes []attributeCtx
514+
}
515+
516+
var attributes []attributeCtx
517+
for name, data := range spec.Locations {
518+
var schemaType string
519+
if len(data.Vars) == 0 {
520+
schemaType = "rsschema.BoolAttribute"
521+
} else {
522+
schemaType = "rsschema.SingleNestedAttribute"
523+
}
524+
525+
var variableAttrs []attributeCtx
526+
for variableName, variable := range data.Vars {
527+
attribute := attributeCtx{
528+
Name: variableName,
529+
Description: variable.Description,
530+
SchemaType: "rsschema.StringAttribute",
531+
Required: false,
532+
Computed: true,
533+
Default: &defaultCtx{
534+
Type: "stringdefault.StaticString",
535+
Value: fmt.Sprintf(`"%s"`, variable.Default),
536+
},
537+
ModifierType: "String",
538+
}
539+
variableAttrs = append(variableAttrs, attribute)
540+
}
541+
542+
var modifierType string
543+
if len(variableAttrs) > 0 {
544+
modifierType = "Object"
545+
} else {
546+
modifierType = "Bool"
547+
}
548+
549+
attribute := attributeCtx{
550+
Name: name,
551+
SchemaType: schemaType,
552+
Description: data.Description,
553+
Required: false,
554+
Attributes: variableAttrs,
555+
ModifierType: modifierType,
556+
}
557+
attributes = append(attributes, attribute)
558+
}
559+
560+
topAttribute := attributeCtx{
561+
Name: "location",
562+
SchemaType: "rsschema.SingleNestedAttribute",
563+
Description: "The location of this object.",
564+
Required: true,
565+
Attributes: attributes,
566+
ModifierType: "Object",
567+
}
568+
569+
type context struct {
570+
StructName string
571+
Schema attributeCtx
572+
}
573+
574+
data := context{
575+
StructName: names.StructName,
576+
Schema: topAttribute,
577+
}
578+
579+
return processTemplate(locationSchemaGetterTmpl, "render-location-schema-getter", data, commonFuncMap)
580+
}
581+
582+
type locationFieldCtx struct {
583+
Name string
584+
Type string
585+
}
586+
587+
type locationCtx struct {
588+
Name string
589+
TerraformStructName string
590+
PangoStructName string
591+
IsBool bool
592+
Fields []locationFieldCtx
593+
}
594+
595+
func renderLocationsGetContext(names *NameProvider, spec *properties.Normalization) []locationCtx {
596+
var locations []locationCtx
597+
598+
for _, location := range spec.Locations {
599+
var fields []locationFieldCtx
600+
for _, variable := range location.Vars {
601+
fields = append(fields, locationFieldCtx{
602+
Name: variable.Name.CamelCase,
603+
Type: "String",
604+
})
605+
}
606+
locations = append(locations, locationCtx{
607+
Name: location.Name.CamelCase,
608+
TerraformStructName: fmt.Sprintf("%s%sLocation", names.StructName, location.Name.CamelCase),
609+
PangoStructName: fmt.Sprintf("%s.%sLocation", names.PackageName, location.Name.CamelCase),
610+
IsBool: len(location.Vars) == 0,
611+
Fields: fields,
612+
})
613+
}
614+
615+
type context struct {
616+
Locations []locationCtx
617+
}
618+
619+
return locations
620+
}
621+
622+
const locationsPangoToState = `
623+
{{- range .Locations }}
624+
{{- if .IsBool }}
625+
if loc.Location.{{ .Name }} {
626+
state.Location.{{ .Name }} = types.BoolValue(true)
627+
}
628+
{{- else }}
629+
if loc.Location.{{ .Name }} != nil {
630+
location := &{{ .TerraformStructName }}{
631+
{{ $locationName := .Name }}
632+
{{- range .Fields }}
633+
{{ .Name }}: types.{{ .Type }}Value(loc.Location.{{ $locationName }}.{{ .Name }}),
634+
{{- end }}
635+
}
636+
state.Location.{{ .Name }} = location
637+
}
638+
{{- end }}
639+
{{- end }}
640+
`
641+
642+
func RenderLocationsPangoToState(names *NameProvider, spec *properties.Normalization) (string, error) {
643+
type context struct {
644+
Locations []locationCtx
645+
}
646+
data := context{Locations: renderLocationsGetContext(names, spec)}
647+
return processTemplate(locationsPangoToState, "render-locations-pango-to-state", data, commonFuncMap)
648+
}
649+
650+
const locationsStateToPango = `
651+
{{- range .Locations }}
652+
{{- if .IsBool }}
653+
if !state.Location.{{ .Name }}.IsNull() && state.Location.{{ .Name }}.ValueBool() {
654+
loc.Location.{{ .Name }} = true
655+
}
656+
{{- else }}
657+
if state.Location.{{ .Name }} != nil {
658+
location := &{{ .PangoStructName }}{
659+
{{ $locationName := .Name }}
660+
{{- range .Fields }}
661+
{{ .Name }}: state.Location.{{ $locationName }}.{{ .Name }}.ValueString(),
662+
{{- end }}
663+
}
664+
loc.Location.{{ .Name }} = location
665+
}
666+
{{- end }}
667+
{{- end }}
668+
`
669+
670+
func RenderLocationsStateToPango(names *NameProvider, spec *properties.Normalization) (string, error) {
671+
type context struct {
672+
Locations []locationCtx
673+
}
674+
data := context{Locations: renderLocationsGetContext(names, spec)}
675+
return processTemplate(locationsStateToPango, "render-locations-state-to-pango", data, commonFuncMap)
676+
}
677+
678+
func ResourceCreateFunction(names *NameProvider, serviceName string, paramSpec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceSDKName string) (string, error) {
379679
funcMap := template.FuncMap{
380-
"ConfigToEntry": ConfigEntry,
680+
"ConfigToEntry": ConfigEntry,
681+
"RenderLocationsStateToPango": func() (string, error) { return RenderLocationsStateToPango(names, paramSpec) },
381682
"ResourceParamToSchema": func(paramName string, paramParameters properties.SpecParam) (string, error) {
382683
return ParamToSchemaResource(paramName, paramParameters, terraformProvider)
383684
},
@@ -388,7 +689,7 @@ func ResourceCreateFunction(structName string, serviceName string, paramSpec *pr
388689
}
389690

390691
data := map[string]interface{}{
391-
"structName": structName,
692+
"structName": names.ResourceStructName,
392693
"serviceName": naming.CamelCase("", serviceName, "", false),
393694
"paramSpec": paramSpec.Spec,
394695
"resourceSDKName": resourceSDKName,
@@ -398,33 +699,42 @@ func ResourceCreateFunction(structName string, serviceName string, paramSpec *pr
398699
return processTemplate(resourceCreateFunction, "resource-create-function", data, funcMap)
399700
}
400701

401-
func ResourceReadFunction(structName string, serviceName string, paramSpec *properties.Normalization, resourceSDKName string) (string, error) {
702+
func ResourceReadFunction(names *NameProvider, serviceName string, paramSpec *properties.Normalization, resourceSDKName string) (string, error) {
402703
if strings.Contains(serviceName, "group") {
403704
serviceName = "group"
404705
}
405706

406707
data := map[string]interface{}{
407-
"structName": structName,
408-
"serviceName": naming.CamelCase("", serviceName, "", false),
409-
"resourceSDKName": resourceSDKName,
410-
"locations": paramSpec.Locations,
708+
"structName": names.StructName,
709+
"resourceStructName": names.ResourceStructName,
710+
"serviceName": naming.CamelCase("", serviceName, "", false),
711+
"resourceSDKName": resourceSDKName,
712+
"locations": paramSpec.Locations,
411713
}
412714

413-
return processTemplate(resourceReadFunction, "resource-read-function", data, nil)
715+
funcMap := template.FuncMap{
716+
"RenderLocationsPangoToState": func() (string, error) { return RenderLocationsPangoToState(names, paramSpec) },
717+
}
718+
719+
return processTemplate(resourceReadFunction, "resource-read-function", data, funcMap)
414720
}
415721

416-
func ResourceUpdateFunction(structName string, serviceName string, paramSpec interface{}, resourceSDKName string) (string, error) {
722+
func ResourceUpdateFunction(names *NameProvider, serviceName string, paramSpec *properties.Normalization, resourceSDKName string) (string, error) {
417723
if strings.Contains(serviceName, "group") {
418724
serviceName = "group"
419725
}
420726

421727
data := map[string]interface{}{
422-
"structName": structName,
728+
"structName": names.ResourceStructName,
423729
"serviceName": naming.CamelCase("", serviceName, "", false),
424730
"resourceSDKName": resourceSDKName,
425731
}
426732

427-
return processTemplate(resourceUpdateFunction, "resource-update-function", data, nil)
733+
funcMap := template.FuncMap{
734+
"RenderLocationsStateToPango": func() (string, error) { return RenderLocationsStateToPango(names, paramSpec) },
735+
}
736+
737+
return processTemplate(resourceUpdateFunction, "resource-update-function", data, funcMap)
428738
}
429739

430740
func ResourceDeleteFunction(structName string, serviceName string, paramSpec interface{}, resourceSDKName string) (string, error) {

pkg/translate/terraform_provider/helper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
// Package-level function map to avoid repetition in each function
1313
var commonFuncMap = template.FuncMap{
1414
"Map": templateMap,
15+
"LowerCase": func(value string) string { return strings.ToLower(value) },
1516
"CamelCaseName": func(paramName string) string { return naming.CamelCase("", paramName, "", true) },
1617
"UnderscoreName": func(paramName string) string { return naming.Underscore("", paramName, "") },
1718
"CamelCaseType": func(paramType string) string { return naming.CamelCase("", paramType, "", true) },

pkg/translate/terraform_provider/struct_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ func TestCreateNestedStruct(t *testing.T) {
5555
createdStructs := make(map[string]bool)
5656

5757
// When
58-
result, err := terraform_provider.CreateNestedStruct(paramName, paramProp, structName, nestedStructString, createdStructs)
58+
err := terraform_provider.CreateNestedStruct(paramName, paramProp, structName, nestedStructString, createdStructs)
5959

6060
// Then
6161
assert.NoError(t, err)
62-
assert.NotEmpty(t, result)
63-
assert.Contains(t, result, "BaseNestedObject")
62+
assert.NotEmpty(t, nestedStructString.String())
63+
assert.Contains(t, nestedStructString.String(), "BaseNestedObject")
6464
}

0 commit comments

Comments
 (0)