Skip to content

Commit

Permalink
Terraform part of importing resources
Browse files Browse the repository at this point in the history
  • Loading branch information
kklimonda-cl committed Aug 16, 2024
1 parent 4708128 commit 4632f9b
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 14 deletions.
10 changes: 10 additions & 0 deletions pkg/properties/normalized.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Import struct {

type ImportLocation struct {
Name *NameVariant
Required bool
XpathElements []string
XpathVariables map[string]ImportXpathVariable
}
Expand Down Expand Up @@ -78,6 +79,14 @@ type NameVariant struct {
LowerCamelCase string
}

func NewNameVariant(name string) *NameVariant {
return &NameVariant{
Underscore: naming.Underscore("", name, ""),
CamelCase: naming.CamelCase("", name, "", true),
LowerCamelCase: naming.CamelCase("", name, "", false),
}
}

type Location struct {
Name *NameVariant
Description string `json:"description" yaml:"description"`
Expand Down Expand Up @@ -579,6 +588,7 @@ func schemaToSpec(object object.Object) (*Normalization, error) {
CamelCase: naming.CamelCase("", location.Name, "", true),
LowerCamelCase: naming.CamelCase("", location.Name, "", false),
},
Required: location.Required,
XpathVariables: xpathVars,
XpathElements: xpath,
}
Expand Down
1 change: 1 addition & 0 deletions pkg/schema/location/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (

type Location struct {
Name string `yaml:"name"`
Required bool `yaml:"required"`
ReadOnly bool `yaml:"read_only"`
Description string `yaml:"description"`
Devices []Device `yaml:"devices"`
Expand Down
190 changes: 181 additions & 9 deletions pkg/translate/terraform_provider/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,32 @@ func RenderLocationStructs(resourceTyp properties.ResourceType, names *NameProvi
}

var fields []fieldCtx

for _, i := range spec.Imports {
if i.Type.CamelCase != data.Name.CamelCase {
continue
}

for _, elt := range i.Locations {
if elt.Required {
fields = append(fields, fieldCtx{
Name: elt.Name.CamelCase,
Type: "types.String",
Tags: []string{fmt.Sprintf("`tfsdk:\"%s\"`", elt.Name.Underscore)},
})
}
}
}

for _, param := range data.Vars {
paramTag := fmt.Sprintf("`tfsdk:\"%s\"`", param.Name.Underscore)
name := param.Name.CamelCase
if name == data.Name.CamelCase {
name = "Name"
paramTag = "`tfsdk:\"name\"`"
}
fields = append(fields, fieldCtx{
Name: param.Name.CamelCase,
Name: name,
Type: "types.String",
Tags: []string{paramTag},
})
Expand All @@ -619,7 +641,7 @@ const locationSchemaGetterTmpl = `
"{{ .Name.Underscore }}": {{ .SchemaType }}{
Description: "{{ .Description }}",
{{- if .Required }}
Required: true
Required: true,
{{- else }}
Optional: true,
{{- end }}
Expand Down Expand Up @@ -709,9 +731,31 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat
}

var variableAttrs []attributeCtx

for _, i := range spec.Imports {
if i.Type.CamelCase != data.Name.CamelCase {
continue
}

for _, elt := range i.Locations {
if elt.Required {
variableAttrs = append(variableAttrs, attributeCtx{
Name: elt.Name,
SchemaType: "rsschema.StringAttribute",
Required: true,
ModifierType: "String",
})
}
}
}

for _, variable := range data.Vars {
name := variable.Name
if name.CamelCase == data.Name.CamelCase {
name = properties.NewNameVariant("name")
}
attribute := attributeCtx{
Name: variable.Name,
Name: name,
Description: variable.Description,
SchemaType: "rsschema.StringAttribute",
Required: false,
Expand Down Expand Up @@ -1371,9 +1415,124 @@ func RenderDataSourceSchema(resourceTyp properties.ResourceType, names *NameProv
return processTemplate(renderSchemaTemplate, "render-resource-schema", data, commonFuncMap)
}

const importLocationAssignmentTmpl = `
var location {{ $.PackageName }}.ImportLocation
{{- range .Specs }}
{{ $type := . }}
if {{ $.LocationVar }}.{{ .Name.CamelCase }} != nil {
{{- range .Locations }}
{{- $pangoStruct := GetPangoStructForLocation $.Variants $type.Name .Name }}
// {{ .Name.CamelCase }}
location = {{ $.PackageName }}.New{{ $pangoStruct }}({{ $.PackageName }}.{{ $pangoStruct }}Spec{
{{- range .Fields }}
{{ . }}: {{ $.LocationVar }}.{{ $type.Name.CamelCase }}.{{ . }}.ValueString(),
{{- end }}
})
{{- end }}
}
{{- end }}
`

func RenderImportLocationAssignment(names *NameProvider, spec *properties.Normalization, locationVar string) (string, error) {
if len(spec.Imports) == 0 {
return "", nil
}

type importVariantSpec struct {
PangoStructNames *map[string]string
}

type importLocationSpec struct {
Name *properties.NameVariant
Fields []string
}

type importSpec struct {
Name *properties.NameVariant
Locations []importLocationSpec
}

var importSpecs []importSpec
variantsByName := make(map[string]importVariantSpec)
for _, elt := range spec.Imports {
existing, found := variantsByName[elt.Type.CamelCase]
if !found {
pangoStructNames := make(map[string]string)
existing = importVariantSpec{
PangoStructNames: &pangoStructNames,
}
}

var locations []importLocationSpec
for _, loc := range elt.Locations {
if !loc.Required {
continue
}

var fields []string
for _, elt := range loc.XpathVariables {
fields = append(fields, elt.Name.CamelCase)
}

pangoStructName := fmt.Sprintf("%s%s%sImportLocation", elt.Variant.CamelCase, elt.Type.CamelCase, loc.Name.CamelCase)
(*existing.PangoStructNames)[loc.Name.CamelCase] = pangoStructName
locations = append(locations, importLocationSpec{
Name: loc.Name,
Fields: fields,
})
}
variantsByName[elt.Type.CamelCase] = existing

importSpecs = append(importSpecs, importSpec{
Name: elt.Type,
Locations: locations,
})
}

type context struct {
PackageName string
LocationVar string
Variants map[string]importVariantSpec
Specs []importSpec
}

data := context{
PackageName: names.PackageName,
LocationVar: locationVar,
Variants: variantsByName,
Specs: importSpecs,
}

funcMap := template.FuncMap{
"GetPangoStructForLocation": func(variants map[string]importVariantSpec, typ *properties.NameVariant, location *properties.NameVariant) (string, error) {
log.Printf("len(variants): %d", len(variants))
for name, elt := range variants {
log.Printf("Type: %s", name)
for name, structName := range *elt.PangoStructNames {
log.Printf(" Name: %s, StructName: %s", name, structName)
}
}
variantSpec, found := variants[typ.CamelCase]
if !found {
return "", fmt.Errorf("failed to find variant for type '%s'", typ.CamelCase)
}

structName, found := (*variantSpec.PangoStructNames)[location.CamelCase]
if !found {
return "", fmt.Errorf("failed to find variant for type '%s', location '%s'", typ.CamelCase, location.CamelCase)
}

return structName, nil
},
}

return processTemplate(importLocationAssignmentTmpl, "render-locations-pango-to-state", data, funcMap)
}

type locationFieldCtx struct {
Name string
Type string
PangoName string
TerraformName string
Type string
}

type locationCtx struct {
Expand All @@ -1390,9 +1549,15 @@ func renderLocationsGetContext(names *NameProvider, spec *properties.Normalizati
for _, location := range spec.Locations {
var fields []locationFieldCtx
for _, variable := range location.Vars {
name := variable.Name.CamelCase
if variable.Name.CamelCase == location.Name.CamelCase {
name = "Name"
}

fields = append(fields, locationFieldCtx{
Name: variable.Name.CamelCase,
Type: "String",
PangoName: variable.Name.CamelCase,
TerraformName: name,
Type: "String",
})
}
locations = append(locations, locationCtx{
Expand All @@ -1418,7 +1583,7 @@ if {{ $.Source }}.{{ .Name }} != nil {
{{ $.Dest }}.{{ .Name }} = &{{ .TerraformStructName }}{
{{ $locationName := .Name }}
{{- range .Fields }}
{{ .Name }}: types.{{ .Type }}Value({{ $.Source }}.{{ $locationName }}.{{ .Name }}),
{{ .TerraformName }}: types.{{ .Type }}Value({{ $.Source }}.{{ $locationName }}.{{ .PangoName }}),
{{- end }}
}
}
Expand Down Expand Up @@ -1447,7 +1612,7 @@ if {{ $.Source }}.{{ .Name }} != nil {
{{ $.Dest }}.{{ .Name }} = &{{ .SdkStructName }}{
{{ $locationName := .Name }}
{{- range .Fields }}
{{ .Name }}: {{ $.Source }}.{{ $locationName }}.{{ .Name }}.ValueString(),
{{ .PangoName }}: {{ $.Source }}.{{ $locationName }}.{{ .TerraformName }}.ValueString(),
{{- end }}
}
}
Expand Down Expand Up @@ -1812,6 +1977,9 @@ func RenderDataSourceStructs(resourceTyp properties.ResourceType, names *NamePro
func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProvider, serviceName string, paramSpec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceSDKName string) (string, error) {
funcMap := template.FuncMap{
"ConfigToEntry": ConfigEntry,
"RenderImportLocationAssignment": func(locationVar string) (string, error) {
return RenderImportLocationAssignment(names, paramSpec, locationVar)
},
"RenderCreateUpdateMovementRequired": func(state string, entries string) (string, error) {
return RendeCreateUpdateMovementRequired(state, entries)
},
Expand Down Expand Up @@ -2090,6 +2258,7 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv
data := map[string]interface{}{
"HasEncryptedResources": paramSpec.HasEncryptedResources(),
"ResourceIsMap": resourceIsMap,
"HasImports": len(paramSpec.Imports) > 0,
"EntryOrConfig": paramSpec.EntryOrConfig(),
"ListAttribute": listAttributeVariant,
"Exhaustive": exhaustive,
Expand All @@ -2100,6 +2269,9 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv
}

funcMap := template.FuncMap{
"RenderImportLocationAssignment": func(locationVar string) (string, error) {
return RenderImportLocationAssignment(names, paramSpec, locationVar)
},
"RenderLocationsStateToPango": func(source string, dest string) (string, error) {
return RenderLocationsStateToPango(names, paramSpec, source, dest)
},
Expand Down
18 changes: 13 additions & 5 deletions pkg/translate/terraform_provider/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,8 @@ const resourceCreateFunction = `
// Perform the operation.
{{- if .HasImports }}
create, err := svc.Create(ctx, loc.Location, nil, *obj)
{{ RenderImportLocationAssignment "state.Location" }}
create, err := svc.Create(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, *obj)
{{- else }}
create, err := svc.Create(ctx, loc.Location, *obj)
{{- end }}
Expand Down Expand Up @@ -1099,14 +1100,13 @@ const resourceReadFunction = `
{{- end }}
resp.Diagnostics.Append(copy_diags...)
{{ RenderLocationsPangoToState "loc.Location" "state.Location" }}
/*
// Keep the timeouts.
// TODO: This won't work for state import.
state.Timeouts = savestate.Timeouts
*/
state.Location = savestate.Location
// Save tfid to state.
state.Tfid = savestate.Tfid
Expand Down Expand Up @@ -2023,7 +2023,13 @@ const resourceDeleteFunction = `
// Perform the operation.
{{- if .HasEntryName }}
if err := svc.Delete(ctx, loc.Location, loc.Name); err != nil && !IsObjectNotFound(err) {
{{- if .HasImports }}
{{ RenderImportLocationAssignment "state.Location" }}
err := svc.Delete(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, loc.Name)
{{- else }}
err := svc.Delete(ctx, loc.Location, loc.Name)
{{- end }}
if err != nil && !IsObjectNotFound(err) {
resp.Diagnostics.AddError("Error in delete", err.Error())
}
{{- else }}
Expand All @@ -2038,7 +2044,9 @@ const resourceDeleteFunction = `
if diags.HasError() {
return
}
if err := svc.Delete(ctx, loc.Location, *obj); err != nil && !IsObjectNotFound(err) {
// HasImports: {{ .HasImports }}
err := svc.Delete(ctx, loc.Location, *obj)
if err != nil && !IsObjectNotFound(err) {
resp.Diagnostics.AddError("Error in delete", err.Error())
}
{{- end }}
Expand Down

0 comments on commit 4632f9b

Please sign in to comment.