diff --git a/assets/terraform/internal/manager/entry_import.go b/assets/terraform/internal/manager/entry_import.go index e98a23fe..d64908ad 100644 --- a/assets/terraform/internal/manager/entry_import.go +++ b/assets/terraform/internal/manager/entry_import.go @@ -2,6 +2,7 @@ package manager import ( "context" + "log/slog" sdkerrors "github.com/PaloAltoNetworks/pango/errors" ) @@ -56,6 +57,7 @@ func (o *ImportableEntryObjectManager[E, L, IL, IS]) Create(ctx context.Context, } obj, err := o.service.Create(ctx, location, importLocs, entry) + slog.Warn("KK: HERE5-1", "obj", obj, "err", err) return obj, err } diff --git a/assets/terraform/internal/manager/utils_test.go b/assets/terraform/internal/manager/utils_test.go index b7b6f480..8545b1a9 100644 --- a/assets/terraform/internal/manager/utils_test.go +++ b/assets/terraform/internal/manager/utils_test.go @@ -100,8 +100,8 @@ func MultiConfig[E sdkmanager.UuidObject](updates *xmlapi.MultiConfig, existingP EntryName: entryName, } - switch MultiConfigOperType(op) { - case MultiConfigOperSet, MultiConfigOperEdit: + switch op { + case "set", "edit": entry := oper.Data.(E) if existing, found := entriesByName[entryName]; found { @@ -123,7 +123,7 @@ func MultiConfig[E sdkmanager.UuidObject](updates *xmlapi.MultiConfig, existingP uuid += 1 idx += 1 } - case MultiConfigOperDelete: + case "delete": fixIndices(entriesByName[entryName].Idx) delete(entriesByName, entryName) idx -= 1 diff --git a/assets/terraform/internal/manager/uuid.go b/assets/terraform/internal/manager/uuid.go index 07cf0ad5..98bc8f58 100644 --- a/assets/terraform/internal/manager/uuid.go +++ b/assets/terraform/internal/manager/uuid.go @@ -439,7 +439,6 @@ func (o *UuidObjectManager[E, L, S]) UpdateMany(ctx context.Context, location L, } else { // If entry from the plan was found in the state check if they match and set the // processedEntry State accordingly. - elt.SetEntryUuid(stateEntry.Entry.EntryUuid()) processedEntry = uuidObjectWithState[E]{ Entry: elt, StateIdx: idx, @@ -493,17 +492,15 @@ func (o *UuidObjectManager[E, L, S]) UpdateMany(ctx context.Context, location L, } processedEntry, found := processedStateEntries[existingEntryName] - if !found { - if exhaustive == Exhaustive { - // If existing entry is not found in the processedStateEntries map, - // entry is not managed by terraform. If Exhaustive update has been - // called, delete it from the server. - updates.Add(&xmlapi.Config{ - Action: "delete", - Xpath: util.AsXpath(path), - Target: o.client.GetTarget(), - }) - } + if !found && exhaustive == Exhaustive { + // If existing entry is not found in the processedStateEntries map, + // entry is not managed by terraform. If Exhaustive update has been + // called, delete it from the server. + updates.Add(&xmlapi.Config{ + Action: "delete", + Xpath: util.AsXpath(path), + Target: o.client.GetTarget(), + }) continue } @@ -645,11 +642,10 @@ func (o *UuidObjectManager[E, L, S]) ReadMany(ctx context.Context, location L, s commonCount := 0 for _, elt := range existing { if stateEntry, found := stateEntriesByName[elt.EntryName()]; found { + commonCount++ stateEntry.State = entryOk stateEntry.Entry = elt - stateEntry.StateIdx = commonCount stateEntriesByName[elt.EntryName()] = stateEntry - commonCount += 1 } } diff --git a/assets/terraform/internal/manager/uuid_test.go b/assets/terraform/internal/manager/uuid_test.go index 7c3634dc..00f4c17b 100644 --- a/assets/terraform/internal/manager/uuid_test.go +++ b/assets/terraform/internal/manager/uuid_test.go @@ -147,37 +147,6 @@ var _ = Describe("Server", func() { }) }) - Context("updating existing entries", func() { - Context("when some of the entries are out of order", func() { - BeforeEach(func() { - initial = []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}} - client = NewMockUuidClient(initial) - service = NewMockUuidService[*MockUuidObject, MockLocation](client) - var ok bool - if mockService, ok = service.(*MockUuidService[*MockUuidObject, MockLocation]); !ok { - panic("failed to cast service to mockService") - } - manager = sdkmanager.NewUuidObjectManager(client, service, MockUuidSpecifier, MockUuidMatcher) - - }) - FIt("should move the entries in order", func() { - entries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "3", Value: "C"}, {Name: "2", Value: "B"}} - - processed, err := manager.ReadMany(ctx, location, entries, sdkmanager.NonExhaustive) - - Expect(err).ToNot(HaveOccurred()) - Expect(processed).To(HaveLen(3)) - Expect(processed).NotTo(MatchEntries(entries)) - - processed, err = manager.UpdateMany(ctx, location, entries, entries, sdkmanager.NonExhaustive, rule.Position{First: &trueVal}) - Expect(err).ToNot(HaveOccurred()) - Expect(processed).To(HaveLen(3)) - - Expect(processed).NotTo(Equal(entries)) - }) - }) - }) - Context("initially has some entries", func() { Context("when creating new entries with NonExhaustive type", func() { Context("and position is set to Last", func() { @@ -185,6 +154,7 @@ var _ = Describe("Server", func() { entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "5", Value: "E"}, {Name: "6", Value: "F"}} processed, err := manager.CreateMany(ctx, location, entries, sdkmanager.NonExhaustive, rule.Position{Last: &trueVal}) + Expect(err).ToNot(HaveOccurred()) Expect(processed).To(HaveLen(3)) diff --git a/assets/terraform/internal/provider/tfid.go b/assets/terraform/internal/provider/tfid.go deleted file mode 100644 index 6a614543..00000000 --- a/assets/terraform/internal/provider/tfid.go +++ /dev/null @@ -1,174 +0,0 @@ -package provider - -import ( - "context" - - "github.com/PaloAltoNetworks/pango" - - "github.com/hashicorp/terraform-plugin-framework/datasource" - dsschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -type genericTfid struct { - Name *string `json:"name,omitempty"` - Names []string `json:"names,omitempty"` - Rules []RuleInfo `json:"rules,omitempty"` - Location map[string]any `json:"location"` -} - -func (g genericTfid) IsValid() error { return nil } - -// Data source. -var ( - _ datasource.DataSource = &tfidDataSource{} - _ datasource.DataSourceWithConfigure = &tfidDataSource{} -) - -func NewTfidDataSource() datasource.DataSource { - return &tfidDataSource{} -} - -type tfidDataSource struct { - client *pango.Client -} - -type tfidDsModel struct { - Location types.String `tfsdk:"location"` - Variables types.Map `tfsdk:"variables"` - Name types.String `tfsdk:"name"` - Names types.List `tfsdk:"names"` - Rules []tfidRuleInfo `tfsdk:"rules"` - - Tfid types.String `tfsdk:"tfid"` -} - -type tfidRuleInfo struct { - Name types.String `tfsdk:"name"` - Uuid types.String `tfsdk:"uuid"` -} - -func (d *tfidDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_tfid" -} - -func (d *tfidDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = dsschema.Schema{ - Description: "Helper data source: create a tfid from the given information. Note that the tfid ouptut from this data source may not exactly match what a resource uses, but it will still be a valid ID to use for resource imports.", - - Attributes: map[string]dsschema.Attribute{ - "location": dsschema.StringAttribute{ - Description: "The location path name.", - Required: true, - }, - "variables": dsschema.MapAttribute{ - Description: "The variables and values for the specified location.", - Optional: true, - ElementType: types.StringType, - }, - "name": dsschema.StringAttribute{ - Description: "(Singleton resource) The config's name.", - Optional: true, - }, - "names": dsschema.ListAttribute{ - Description: "(Grouping resources) The names of the configs.", - Optional: true, - ElementType: types.StringType, - }, - "tfid": dsschema.StringAttribute{ - Description: "The tfid created from the given parts.", - Computed: true, - }, - "rules": dsschema.ListNestedAttribute{ - Description: "(UUID enabled resources) The ordered list of rule names paried with their respective UUIDs.", - Optional: true, - NestedObject: dsschema.NestedAttributeObject{ - Attributes: map[string]dsschema.Attribute{ - "name": dsschema.StringAttribute{ - Description: "The rule name.", - Required: true, - }, - "uuid": dsschema.StringAttribute{ - Description: "The rule UUID.", - Required: true, - }, - }, - }, - }, - }, - } -} - -func (d *tfidDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - - d.client = req.ProviderData.(*pango.Client) -} - -func (d *tfidDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var state tfidDsModel - resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Basic logging. - tflog.Info(ctx, "performing data source read", map[string]any{ - "data_source_name": "panos_tfid", - }) - - vars := make(map[string]types.String, len(state.Variables.Elements())) - resp.Diagnostics.Append(state.Variables.ElementsAs(ctx, &vars, false).Errors()...) - - var names []string - resp.Diagnostics.Append(state.Names.ElementsAs(ctx, &names, false)...) - - if resp.Diagnostics.HasError() { - return - } - - tfid := genericTfid{ - Name: state.Name.ValueStringPointer(), - Names: append([]string(nil), names...), - } - - if len(vars) == 0 { - tfid.Location = map[string]any{ - state.Location.ValueString(): true, - } - } else { - content := make(map[string]string) - for key, value := range vars { - content[key] = value.ValueString() - } - tfid.Location = map[string]any{ - state.Location.ValueString(): content, - } - } - - if len(state.Rules) > 0 { - tfid.Rules = make([]RuleInfo, 0, len(state.Rules)) - for _, x := range state.Rules { - tfid.Rules = append(tfid.Rules, RuleInfo{ - Name: x.Name.ValueString(), - Uuid: x.Uuid.ValueString(), - }) - } - } - - // Encode the tfid from the info given. - idstr, err := EncodeLocation(tfid) - if err != nil { - resp.Diagnostics.AddError("error encoding tfid", err.Error()) - return - } - - // Set the tfid param. - state.Tfid = types.StringValue(idstr) - - // Done. - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index aa9133b2..6ac2f444 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -74,6 +74,7 @@ func (c *Command) Execute() error { } var resourceList []string var dataSourceList []string + names := make(map[string]string) for _, specPath := range c.specs { log.Printf("Parsing %s...\n", specPath) @@ -126,13 +127,17 @@ func (c *Command) Execute() error { } terraformGenerator := generate.NewCreator(config.Output.TerraformProvider, c.templatePath, spec) - dataSources, resources, err := terraformGenerator.RenderTerraformProviderFile(spec, resourceTyp) + dataSources, resources, partialNames, err := terraformGenerator.RenderTerraformProviderFile(spec, resourceTyp) if err != nil { return fmt.Errorf("error rendering Terraform provider file for %s - %s", specPath, err) } resourceList = append(resourceList, resources...) dataSourceList = append(dataSourceList, dataSources...) + + for k, v := range partialNames { + names[k] = v + } } if pluralVariant { @@ -149,13 +154,17 @@ func (c *Command) Execute() error { } terraformGenerator := generate.NewCreator(config.Output.TerraformProvider, c.templatePath, spec) - dataSources, resources, err := terraformGenerator.RenderTerraformProviderFile(spec, resourceTyp) + dataSources, resources, partialNames, err := terraformGenerator.RenderTerraformProviderFile(spec, resourceTyp) if err != nil { return fmt.Errorf("error rendering Terraform provider file for %s - %s", specPath, err) } resourceList = append(resourceList, resources...) dataSourceList = append(dataSourceList, dataSources...) + + for k, v := range partialNames { + names[k] = v + } } } else if c.commandType == properties.CommandTypeSDK && !spec.GoSdkSkip { generator := generate.NewCreator(config.Output.GoSdk, c.templatePath, spec) @@ -173,6 +182,7 @@ func (c *Command) Execute() error { newProviderObject := properties.NewTerraformProviderFile(providerSpec.Name) newProviderObject.DataSources = append(newProviderObject.DataSources, dataSourceList...) newProviderObject.Resources = append(newProviderObject.Resources, resourceList...) + newProviderObject.Names = names terraformGenerator := generate.NewCreator(config.Output.TerraformProvider, c.templatePath, providerSpec) err = terraformGenerator.RenderTerraformProvider(newProviderObject, providerSpec, config.TerraformProviderConfig) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index ea177daa..38b8f0e6 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -57,7 +57,7 @@ func (c *Creator) RenderTemplate() error { } // RenderTerraformProviderFile generates a Go file for a Terraform provider based on the provided TerraformProviderFile and Normalization arguments. -func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, typ properties.ResourceType) ([]string, []string, error) { +func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, typ properties.ResourceType) ([]string, []string, map[string]string, error) { var name string switch typ { case properties.ResourceUuidPlural: @@ -72,19 +72,19 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty tfp := terraform_provider.GenerateTerraformProvider{} if err := tfp.GenerateTerraformDataSource(typ, spec, terraformProvider); err != nil { - return nil, nil, err + return nil, nil, nil, err } if err := tfp.GenerateTerraformResource(typ, spec, terraformProvider); err != nil { - return nil, nil, err + return nil, nil, nil, err } if err := tfp.GenerateCommonCode(typ, spec, terraformProvider); err != nil { - return nil, nil, err + return nil, nil, nil, err } if err := tfp.GenerateTerraformProviderFile(spec, terraformProvider); err != nil { - return nil, nil, err + return nil, nil, nil, err } var filePath string @@ -100,10 +100,10 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty } if err := c.writeFormattedContentToFile(filePath, terraformProvider.Code.String()); err != nil { - return nil, nil, err + return nil, nil, nil, err } - return terraformProvider.DataSources, terraformProvider.Resources, nil + return terraformProvider.DataSources, terraformProvider.Resources, terraformProvider.Names, nil } // RenderTerraformProvider generates and writes a Terraform provider file. diff --git a/pkg/properties/provider_file.go b/pkg/properties/provider_file.go index f6162480..bd78c71a 100644 --- a/pkg/properties/provider_file.go +++ b/pkg/properties/provider_file.go @@ -19,10 +19,16 @@ func NewTerraformProviderFile(filename string) *TerraformProviderFile { ImportManager: imports.NewManager(), DataSources: make([]string, 0, 10), Resources: make([]string, 0, 10), + Names: make(map[string]string), Code: &code, } } +type TerraformFuncData struct { + KeyName string + StructName string +} + // TerraformProviderFile is a Terraform provider file handler. type TerraformProviderFile struct { Filename string @@ -30,5 +36,6 @@ type TerraformProviderFile struct { ImportManager *imports.Manager DataSources []string Resources []string + Names map[string]string Code *strings.Builder } diff --git a/pkg/translate/terraform_provider/device_group_parent_crud.go b/pkg/translate/terraform_provider/device_group_parent_crud.go index c1bc7f42..daecdd5f 100644 --- a/pkg/translate/terraform_provider/device_group_parent_crud.go +++ b/pkg/translate/terraform_provider/device_group_parent_crud.go @@ -167,7 +167,6 @@ if err := assignParent(ctx, r.client, deviceGroup, parent); err != nil { return } -state.Tfid = types.StringValue("") resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) ` const deviceGroupParentResourceUpdate = deviceGroupParentResourceCreate diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index f191a646..c30d274a 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -910,6 +910,95 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat return processTemplate(locationSchemaGetterTmpl, "render-location-schema-getter", data, commonFuncMap) } +type marshallerFieldSpec struct { + Name string + Type string + StructName string + Tags string +} + +type marshallerSpec struct { + StructName string + Fields []marshallerFieldSpec +} + +func createLocationMarshallerSpecs(names *NameProvider, spec *properties.Normalization) []marshallerSpec { + var specs []marshallerSpec + + var topFields []marshallerFieldSpec + for _, loc := range spec.Locations { + if len(loc.Vars) == 0 { + topFields = append(topFields, marshallerFieldSpec{ + Name: loc.Name.CamelCase, + Type: "bool", + Tags: fmt.Sprintf("`json:\"%s\"`", loc.Name.Underscore), + }) + continue + } + + topFields = append(topFields, marshallerFieldSpec{ + Name: loc.Name.CamelCase, + Type: "object", + StructName: fmt.Sprintf("%s%sLocation", names.StructName, loc.Name.CamelCase), + Tags: fmt.Sprintf("`json:\"%s\"`", loc.Name.Underscore), + }) + + var fields []marshallerFieldSpec + for _, field := range loc.Vars { + name := field.Name.CamelCase + tag := field.Name.Underscore + if name == loc.Name.CamelCase { + name = "Name" + tag = "name" + } + + fields = append(fields, marshallerFieldSpec{ + Name: name, + Type: "string", + Tags: fmt.Sprintf("`json:\"%s\"`", tag), + }) + } + + // Add import location (e.g. vsys) name to location + for _, i := range spec.Imports { + if i.Type.CamelCase != loc.Name.CamelCase { + continue + } + + for _, elt := range i.Locations { + if elt.Required { + fields = append(fields, marshallerFieldSpec{ + Name: elt.Name.CamelCase, + Type: "string", + Tags: fmt.Sprintf("`tfsdk:\"%s\"`", elt.Name.Underscore), + }) + } + } + } + + specs = append(specs, marshallerSpec{ + StructName: fmt.Sprintf("%s%sLocation", names.StructName, loc.Name.CamelCase), + Fields: fields, + }) + } + + specs = append(specs, marshallerSpec{ + StructName: fmt.Sprintf("%sLocation", names.StructName), + Fields: topFields, + }) + + return specs +} + +func RenderLocationMarshallers(names *NameProvider, spec *properties.Normalization) (string, error) { + var context struct { + Specs []marshallerSpec + } + context.Specs = createLocationMarshallerSpecs(names, spec) + + return processTemplate(locationMarshallersTmpl, "render-location-marshallers", context, commonFuncMap) +} + func RenderCustomImports(spec *properties.Normalization) string { template, _ := getCustomTemplateForFunction(spec, "Imports") return template @@ -984,7 +1073,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager } var validators *validatorCtx - if idx == 0 { + if schemaTyp == schemaResource && idx == 0 { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") @@ -1224,20 +1313,6 @@ func createSchemaSpecForEntrySingularModel(resourceTyp properties.ResourceType, SchemaType: "SingleNestedAttribute", }) - tfid := &properties.NameVariant{ - Underscore: naming.Underscore("", "tfid", ""), - CamelCase: naming.CamelCase("", "tfid", "", true), - LowerCamelCase: naming.CamelCase("", "tfid", "", false), - } - - attributes = append(attributes, attributeCtx{ - Package: packageName, - Name: tfid, - SchemaType: "StringAttribute", - Description: "The Terraform ID.", - Computed: true, - }) - normalizationAttrs, normalizationSchemas := createSchemaSpecForNormalization(resourceTyp, schemaTyp, spec, packageName, structName, manager) attributes = append(attributes, normalizationAttrs...) @@ -1433,7 +1508,7 @@ func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schem continue } var validators *validatorCtx - if idx == 0 { + if schemaTyp == schemaResource && idx == 0 { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") @@ -1649,16 +1724,15 @@ func RenderDataSourceSchema(resourceTyp properties.ResourceType, names *NameProv } const importLocationAssignmentTmpl = ` -var location {{ $.PackageName }}.ImportLocation {{- range .Specs }} {{ $type := . }} -if {{ $.LocationVar }}.{{ .Name.CamelCase }} != nil { +if {{ $.Source }}.{{ .Name.CamelCase }} != nil { {{- range .Locations }} {{- $pangoStruct := GetPangoStructForLocation $.Variants $type.Name .Name }} // {{ .Name.CamelCase }} - location = {{ $.PackageName }}.New{{ $pangoStruct }}({{ $.PackageName }}.{{ $pangoStruct }}Spec{ + {{ $.Dest }} = {{ $.PackageName }}.New{{ $pangoStruct }}({{ $.PackageName }}.{{ $pangoStruct }}Spec{ {{- range .Fields }} - {{ . }}: {{ $.LocationVar }}.{{ $type.Name.CamelCase }}.{{ . }}.ValueString(), + {{ . }}: {{ $.Source }}.{{ $type.Name.CamelCase }}.{{ . }}.ValueString(), {{- end }} }) {{- end }} @@ -1666,7 +1740,7 @@ if {{ $.LocationVar }}.{{ .Name.CamelCase }} != nil { {{- end }} ` -func RenderImportLocationAssignment(names *NameProvider, spec *properties.Normalization, locationVar string) (string, error) { +func RenderImportLocationAssignment(names *NameProvider, spec *properties.Normalization, source string, dest string) (string, error) { if len(spec.Imports) == 0 { return "", nil } @@ -1724,14 +1798,16 @@ func RenderImportLocationAssignment(names *NameProvider, spec *properties.Normal type context struct { PackageName string - LocationVar string + Source string + Dest string Variants map[string]importVariantSpec Specs []importSpec } data := context{ PackageName: names.PackageName, - LocationVar: locationVar, + Source: source, + Dest: dest, Variants: variantsByName, Specs: importSpecs, } @@ -2106,12 +2182,6 @@ func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTy var fields []datasourceStructFieldSpec - fields = append(fields, datasourceStructFieldSpec{ - Name: "Tfid", - Type: "types.String", - Tags: []string{"`tfsdk:\"tfid\"`"}, - }) - fields = append(fields, datasourceStructFieldSpec{ Name: "Location", Type: fmt.Sprintf("%sLocation", names.StructName), @@ -2243,8 +2313,8 @@ func getCustomTemplateForFunction(spec *properties.Normalization, function strin 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) + "RenderImportLocationAssignment": func(source string, dest string) (string, error) { + return RenderImportLocationAssignment(names, paramSpec, source, dest) }, "RenderCreateUpdateMovementRequired": func(state string, entries string) (string, error) { return RendeCreateUpdateMovementRequired(state, entries) @@ -2565,8 +2635,8 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv } funcMap := template.FuncMap{ - "RenderImportLocationAssignment": func(locationVar string) (string, error) { - return RenderImportLocationAssignment(names, paramSpec, locationVar) + "RenderImportLocationAssignment": func(source string, dest string) (string, error) { + return RenderImportLocationAssignment(names, paramSpec, source, dest) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { return RenderLocationsStateToPango(names, paramSpec, source, dest) @@ -2576,6 +2646,142 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv return processTemplate(tmpl, "resource-delete-function", data, funcMap) } +type importStateStructFieldSpec struct { + Name string + Type string + Tags string +} + +type importStateStructSpec struct { + StructName string + Fields []importStateStructFieldSpec +} + +func createImportStateStructSpecs(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) []importStateStructSpec { + var specs []importStateStructSpec + + var fields []importStateStructFieldSpec + fields = append(fields, importStateStructFieldSpec{ + Name: "Location", + Type: fmt.Sprintf("%sLocation", names.StructName), + Tags: "`json:\"location\"`", + }) + + switch resourceTyp { + case properties.ResourceEntry: + fields = append(fields, importStateStructFieldSpec{ + Name: "Name", + Type: "string", + Tags: "`json:\"name\"`", + }) + case properties.ResourceEntryPlural, properties.ResourceUuid, properties.ResourceUuidPlural: + fields = append(fields, importStateStructFieldSpec{ + Name: "Names", + Type: "[]string", + Tags: "`json:\"names\"`", + }) + } + + specs = append(specs, importStateStructSpec{ + StructName: fmt.Sprintf("%sImportState", names.StructName), + Fields: fields, + }) + + return specs +} + +func RenderImportStateStructs(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) { + type context struct { + Specs []importStateStructSpec + } + + data := context{ + Specs: createImportStateStructSpecs(resourceTyp, names, spec), + } + + return processTemplate(renderImportStateStructsTmpl, "render-import-state-structs", data, nil) +} + +func ResourceImportStateFunction(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) { + type context struct { + StructName string + ResourceIsMap bool + ResourceIsList bool + HasEntryName bool + ListAttribute *properties.NameVariant + } + + data := context{ + StructName: names.StructName, + } + + switch resourceTyp { + case properties.ResourceEntry: + data.HasEntryName = spec.HasEntryName() + case properties.ResourceEntryPlural: + data.ResourceIsMap = true + data.ListAttribute = properties.NewNameVariant(spec.TerraformProviderConfig.PluralName) + case properties.ResourceUuid, properties.ResourceUuidPlural: + data.ResourceIsList = true + data.ListAttribute = properties.NewNameVariant(spec.TerraformProviderConfig.PluralName) + } + + return processTemplate(resourceImportStateFunctionTmpl, "resource-import-state-function", data, nil) +} + +func RenderImportStateCreator(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) { + type context struct { + ResourceIsMap bool + ResourceIsList bool + ListAttribute *properties.NameVariant + ListAttributeType string + FuncName string + ModelName string + StructName string + Fields []marshallerFieldSpec + } + + var fields []marshallerFieldSpec + fields = append(fields, marshallerFieldSpec{ + Name: "Location", + Type: "object", + }) + + var listAttribute *properties.NameVariant + var listAttributeType string + var isMap, isList bool + switch resourceTyp { + case properties.ResourceEntry: + if spec.HasEntryName() { + fields = append(fields, marshallerFieldSpec{ + Name: "Name", + Type: "string", + }) + } + case properties.ResourceEntryPlural: + isMap = true + listAttribute = properties.NewNameVariant(spec.TerraformProviderConfig.PluralName) + listAttributeType = fmt.Sprintf("%s%sObject", names.ResourceStructName, listAttribute.CamelCase) + case properties.ResourceUuid, properties.ResourceUuidPlural: + isList = true + listAttribute = properties.NewNameVariant(spec.TerraformProviderConfig.PluralName) + listAttributeType = fmt.Sprintf("%s%sObject", names.ResourceStructName, listAttribute.CamelCase) + } + + data := context{ + ResourceIsMap: isMap, + ResourceIsList: isList, + ListAttribute: listAttribute, + ListAttributeType: listAttributeType, + FuncName: fmt.Sprintf("%sImportStateCreator", names.StructName), + ModelName: fmt.Sprintf("%sModel", names.ResourceStructName), + StructName: fmt.Sprintf("%sImportState", names.StructName), + Fields: fields, + } + + return processTemplate(resourceImportStateCreatorTmpl, "render-import-state-creator", data, commonFuncMap) +} + func ConfigEntry(entryName string, param *properties.SpecParam) (string, error) { var entries []Entry @@ -2598,6 +2804,32 @@ func ConfigEntry(entryName string, param *properties.SpecParam) (string, error) return processTemplate(resourceConfigEntry, "config-entry", entryData, nil) } +func RenderResourceFuncMap(names map[string]string) (string, error) { + type entry struct { + Key string + StructName string + } + type context struct { + Entries []entry + } + + var entries []entry + for key, name := range names { + if key == "" { + continue + } + entries = append(entries, entry{ + Key: fmt.Sprintf("panos%s", key), + StructName: name, + }) + } + data := context{ + Entries: entries, + } + + return processTemplate(resourceFuncMapTmpl, "resource-func-map", data, nil) +} + var customResourceFuncsMap = map[string]map[string]string{ "device_group_parent": { "Imports": deviceGroupParentImports, diff --git a/pkg/translate/terraform_provider/template.go b/pkg/translate/terraform_provider/template.go index 5498e0c4..f040bcbf 100644 --- a/pkg/translate/terraform_provider/template.go +++ b/pkg/translate/terraform_provider/template.go @@ -107,10 +107,6 @@ const resourceSchemaLocationAttribute = ` }, }, }, - "tfid": rsschema.StringAttribute{ - Description: "The Terraform ID.", - Computed: true, - }, ` const resourceCreateUpdateMovementRequiredTmpl = ` @@ -242,6 +238,11 @@ var ( ) func New{{ resourceStructName }}() resource.Resource { + if _, found := resourceFuncMap["panos{{ metaName }}"]; !found { + resourceFuncMap["panos{{ metaName }}"] = resourceFuncs{ + CreateImportId: {{ structName }}ImportStateCreator, + } + } return &{{ resourceStructName }}{} } @@ -260,21 +261,6 @@ type {{ resourceStructName }} struct { {{- end }} } -{{- if not GoSDKSkipped }} -type {{ resourceStructName }}Tfid struct { - {{ CreateTfIdStruct }} -} - -func (o *{{ resourceStructName }}Tfid) IsValid() error { - {{- if .HasEntryName }} - if o.Name == "" { - return fmt.Errorf("name is unspecified") - } - {{- end }} - return o.Location.IsValid() -} -{{- end }} - func {{ resourceStructName }}LocationSchema() rsschema.Attribute { return {{ structName }}LocationSchema() } @@ -368,8 +354,12 @@ func (r *{{ resourceStructName }}) Delete(ctx context.Context, req resource.Dele {{ ResourceDeleteFunction resourceStructName serviceName}} } +{{ RenderImportStateStructs }} + +{{ RenderImportStateCreator }} + func (r *{{ resourceStructName }}) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("tfid"), req, resp) + {{ ResourceImportStateFunction }} } {{- /* Done */ -}}` @@ -592,17 +582,11 @@ const resourceCreateFunction = ` } // Determine the location. -{{- if .HasEntryName }} - loc := {{ .structName }}Tfid{Name: state.Name.ValueString()} -{{- else }} - loc := {{ .structName }}Tfid{} -{{- end }} - - // TODO: this needs to handle location structure for UUID style shared has nested structure type - {{ RenderLocationsStateToPango "state.Location" "loc.Location" }} + var location {{ .resourceSDKName }}.Location + {{ RenderLocationsStateToPango "state.Location" "location" }} - if err := loc.IsValid(); err != nil { + if err := location.IsValid(); err != nil { resp.Diagnostics.AddError("Invalid location", err.Error()) return } @@ -627,26 +611,17 @@ const resourceCreateFunction = ` // Perform the operation. {{- if .HasImports }} - {{ RenderImportLocationAssignment "state.Location" }} - created, err := r.manager.Create(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, obj) + var importLocation {{ .resourceSDKName }}.ImportLocation + {{ RenderImportLocationAssignment "state.Location" "importLocation" }} + created, err := r.manager.Create(ctx, location, []{{ .resourceSDKName }}.ImportLocation{importLocation}, obj) {{- else }} - created, err := r.manager.Create(ctx, loc.Location, obj) + created, err := r.manager.Create(ctx, location, obj) {{- end }} if err != nil { resp.Diagnostics.AddError("Error in create", err.Error()) return } - // Tfid handling. - tfid, err := EncodeLocation(&loc) - if err != nil { - resp.Diagnostics.AddError("Error creating tfid", err.Error()) - return - } - - // Save the state. - state.Tfid = types.StringValue(tfid) - resp.Diagnostics.Append(state.CopyFromPango(ctx, created, {{ $ev }})...) if resp.Diagnostics.HasError() { return @@ -866,20 +841,8 @@ const resourceReadFunction = ` return } -{{- if eq .ResourceOrDS "DataSource" }} - var loc {{ .dataSourceStructName }}Tfid - {{- if .HasEntryName }} - loc.Name = *savestate.Name.ValueStringPointer() - {{- end }} - {{ RenderLocationsStateToPango "savestate.Location" "loc.Location" }} -{{- else }} - var loc {{ .resourceStructName }}Tfid - // Parse the location from tfid. - if err := DecodeLocation(savestate.Tfid.ValueString(), &loc); err != nil { - resp.Diagnostics.AddError("Error parsing tfid", err.Error()) - return - } -{{- end }} + var location {{ .resourceSDKName }}.Location + {{ RenderLocationsStateToPango "savestate.Location" "location" }} {{ $ev := "nil" }} {{- if .HasEncryptedResources }} @@ -896,19 +859,18 @@ const resourceReadFunction = ` "resource_name": "panos_{{ UnderscoreName .resourceStructName }}", "function": "Read", {{- if .HasEntryName }} - "name": loc.Name, + "name": savestate.Name.ValueString(), {{- end }} }) // Perform the operation. {{- if .HasEntryName }} - object, err := o.manager.Read(ctx, loc.Location, loc.Name) + object, err := o.manager.Read(ctx, location, savestate.Name.ValueString()) {{- else }} - object, err := o.manager.Read(ctx, loc.Location) + object, err := o.manager.Read(ctx, location) {{- end }} if err != nil { - tflog.Warn(ctx, "KK: HERE3-1", map[string]any{"Error": err.Error()}) if errors.Is(err, sdkmanager.ErrObjectNotFound) { {{- if eq .ResourceOrDS "DataSource" }} resp.Diagnostics.AddError("Error reading data", err.Error()) @@ -931,10 +893,6 @@ const resourceReadFunction = ` */ state.Location = savestate.Location - // Save tfid to state. - state.Tfid = savestate.Tfid - - // Save the answer to state. {{- if .HasEncryptedResources }} ev_map, ev_diags := types.MapValueFrom(ctx, types.StringType, ev) @@ -1151,17 +1109,13 @@ const resourceUpdateFunction = ` } {{- end }} - var loc {{ .structName }}Tfid - if err := DecodeLocation(state.Tfid.ValueString(), &loc); err != nil { - resp.Diagnostics.AddError("Error parsing tfid", err.Error()) - return - } + var location {{ .resourceSDKName }}.Location + {{ RenderLocationsStateToPango "state.Location" "location" }} // Basic logging. tflog.Info(ctx, "performing resource update", map[string]any{ "resource_name": "panos_{{ UnderscoreName .structName }}", "function": "Update", - "tfid": state.Tfid.ValueString(), }) // Verify mode. @@ -1171,9 +1125,9 @@ const resourceUpdateFunction = ` } {{- if .HasEntryName }} - obj, err := r.manager.Read(ctx, loc.Location, loc.Name) + obj, err := r.manager.Read(ctx, location, plan.Name.ValueString()) {{- else }} - obj, err := r.manager.Read(ctx, loc.Location) + obj, err := r.manager.Read(ctx, location) {{- end }} if err != nil { resp.Diagnostics.AddError("Error in update", err.Error()) @@ -1187,9 +1141,9 @@ const resourceUpdateFunction = ` // Perform the operation. {{- if .HasEntryName }} - updated, err := r.manager.Update(ctx, loc.Location, obj, loc.Name) + updated, err := r.manager.Update(ctx, location, obj, obj.Name) {{- else }} - updated, err := r.manager.Update(ctx, loc.Location, obj) + updated, err := r.manager.Update(ctx, location, obj) {{- end }} if err != nil { resp.Diagnostics.AddError("Error in update", err.Error()) @@ -1204,16 +1158,6 @@ const resourceUpdateFunction = ` state.Timeouts = plan.Timeouts */ - // Save the tfid. -{{- if .HasEntryName }} - loc.Name = obj.Name -{{- end }} - tfid, err := EncodeLocation(&loc) - if err != nil { - resp.Diagnostics.AddError("error creating tfid", err.Error()) - return - } - state.Tfid = types.StringValue(tfid) copy_diags := state.CopyFromPango(ctx, updated, {{ $ev }}) {{- if .HasEncryptedResources }} @@ -1290,19 +1234,12 @@ const resourceDeleteFunction = ` return } - // Parse the location from tfid. - var loc {{ .structName }}Tfid - if err := DecodeLocation(state.Tfid.ValueString(), &loc); err != nil { - resp.Diagnostics.AddError("error parsing tfid", err.Error()) - return - } - // Basic logging. tflog.Info(ctx, "performing resource delete", map[string]any{ "resource_name": "panos_{{ UnderscoreName .structName }}", "function": "Delete", {{- if .HasEntryName }} - "name": loc.Name, + "name": state.Name.ValueString(), {{- end }} }) @@ -1312,12 +1249,16 @@ const resourceDeleteFunction = ` return } + var location {{ .resourceSDKName }}.Location + {{ RenderLocationsStateToPango "state.Location" "location" }} + {{- if .HasEntryName }} {{- if .HasImports }} - {{ RenderImportLocationAssignment "state.Location" }} - err := r.manager.Delete(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, []string{loc.Name}, sdkmanager.NonExhaustive) + var importLocation {{ .resourceSDKName }}.ImportLocation + {{ RenderImportLocationAssignment "state.Location" "importLocation" }} + err := r.manager.Delete(ctx, location, []{{ .resourceSDKName }}.ImportLocation{importLocation}, []string{state.Name.ValueString()}, sdkmanager.NonExhaustive) {{- else }} - err := r.manager.Delete(ctx, loc.Location, []string{loc.Name}) + err := r.manager.Delete(ctx, location, []string{state.Name.ValueString()}) {{- end }} if err != nil && !errors.Is(err, sdkmanager.ErrObjectNotFound) { resp.Diagnostics.AddError("Error in delete", err.Error()) @@ -1335,18 +1276,169 @@ const resourceDeleteFunction = ` return } - err := r.manager.Delete(ctx, loc.Location, obj) + err := r.manager.Delete(ctx, location, obj) if err != nil && errors.Is(err, sdkmanager.ErrObjectNotFound) { resp.Diagnostics.AddError("Error in delete", err.Error()) } {{- end }} ` +const renderImportStateStructsTmpl = ` +{{- range .Specs }} +type {{ .StructName }} struct { + {{- range .Fields }} + {{ .Name }} {{ .Type }} {{ .Tags }} + {{- end }} +} +{{- end }} +` + +const locationMarshallersTmpl = ` +{{- define "renderMarshallerField" }} + {{- if eq .Type "object" }} + {{ .Name }}: o.{{ .Name }}, + {{- else }} + {{ .Name }}: o.{{ .Name }}.Value{{ .Type | CamelCaseName }}Pointer(), + {{- end }} +{{- end }} + +{{- define "renderShadowStructField" }} + {{- if eq .Type "object" }} + {{ .Name }} *{{ .StructName }} {{ .Tags }} + {{- else }} + {{ .Name }} *{{ .Type }} {{ .Tags }} + {{- end }} +{{- end }} + +{{- define "renderUnmarshallerField" }} + {{- if eq .Type "object" }} + o.{{ .Name }} = shadow.{{ .Name }} + {{- else }} + o.{{ .Name }} = types.{{ .Type | CamelCaseName }}PointerValue(shadow.{{ .Name }}) + {{- end }} +{{- end }} + +{{- range .Specs }} + {{- $spec := . }} +func (o {{ .StructName }}) MarshalJSON() ([]byte, error) { + obj := struct { + {{- range .Fields }} + {{- template "renderShadowStructField" . }} + {{- end }} + }{ + {{- range .Fields }} + {{- template "renderMarshallerField" . }} + {{- end }} + } + + return json.Marshal(obj) +} + +func (o *{{ .StructName }}) UnmarshalJSON(data []byte) error { + var shadow struct { + {{- range .Fields }} + {{- template "renderShadowStructField" . }} + {{- end }} + } + + err := json.Unmarshal(data, &shadow) + if err != nil { + return err + } + + {{- range .Fields }} + {{- template "renderUnmarshallerField" . }} + {{- end }} + + return nil +} +{{- end }} +` + +const resourceImportStateCreatorTmpl = ` +func {{ .FuncName }}(ctx context.Context, resource types.Object) ([]byte, error) { + var object {{ .ModelName }} + diags := resource.As(ctx, &object, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("failed to convert types.Object into Model") + } + + {{- if $.ResourceIsMap }} + var names []string + { + var elements map[string]{{ .ListAttributeType }} + diags := object.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false) + if diags.HasError() { + return nil, fmt.Errorf("failed to transform elements into a map of entries") + } + for name := range elements { + names = append(names, name) + } + } + + + {{- else if $.ResourceIsList }} + var names []string + { + var elements []{{ .ListAttributeType }} + diags := object.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false) + if diags.HasError() { + return nil, fmt.Errorf("failed to transform elements into a map of entries") + } + for _, elt := range elements { + names = append(names, elt.Name.ValueString()) + } + } + {{- end }} + importStruct := {{ .StructName }}{ + {{- range .Fields }} + {{- if eq .Type "object" }} + {{ .Name }}: object.{{ .Name }}, + {{- else }} + {{ .Name }}: object.{{ .Name }}.Value{{ .Type | CamelCaseName }}(), + {{- end }} + {{- if or $.ResourceIsMap $.ResourceIsList }} + Names: names, + {{- end }} + {{- end }} + } + + return json.Marshal(importStruct) +} +` + +const resourceImportStateFunctionTmpl = ` + var obj {{ .StructName }}ImportState + data, err := base64.StdEncoding.DecodeString(req.ID) + if err != nil { + resp.Diagnostics.AddError("Failed to decode Import ID", err.Error()) + return + } + + err = json.Unmarshal(data, &obj) + if err != nil { + resp.Diagnostics.AddError("Failed to unmarshal Import ID", err.Error()) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("location"), obj.Location)...) + +{{- if .ResourceIsMap }} + +{{- else if .ResourceIsList }} + +{{- else if .HasEntryName }} + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), obj.Name)...) +{{- end }} +` + const commonTemplate = ` {{- RenderLocationStructs }} {{- RenderLocationSchemaGetter }} +{{- RenderLocationMarshallers }} + {{- RenderCustomCommonCode }} ` @@ -1360,7 +1452,7 @@ var ( ) func New{{ dataSourceStructName }}() datasource.DataSource { - return &{{ dataSourceStructName }}{} + return &{{ dataSourceStructName }}{} } type {{ dataSourceStructName }} struct { @@ -1383,22 +1475,6 @@ type {{ dataSourceStructName }}Filter struct { //TODO: Generate Data Source filter via function } -{{- if not GoSDKSkipped }} -type {{ dataSourceStructName }}Tfid struct { - {{ CreateTfIdStruct }} -} - -func (o *{{ dataSourceStructName }}Tfid) IsValid() error { - {{- if .HasEntryName }} - if o.Name == "" { - return fmt.Errorf("name is unspecified") - } - {{- end }} - return o.Location.IsValid() -} -{{- end }} - - {{ RenderDataSourceStructs }} {{ RenderCopyToPangoFunctions }} @@ -1583,7 +1659,6 @@ func (p *PanosProvider) Configure(ctx context.Context, req provider.ConfigureReq // DataSources defines the data sources for this provider. func (p *PanosProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - NewTfidDataSource, {{- range $fnName := DataSources }} New{{ $fnName }}, {{- end }} @@ -1602,6 +1677,7 @@ func (p *PanosProvider) Resources(_ context.Context) []func() resource.Resource func (p *PanosProvider) Functions(_ context.Context) []func() function.Function { return []func() function.Function{ NewAddressValueFunction, + NewCreateImportIdFunction, } } @@ -1614,4 +1690,22 @@ func New(version string) func() provider.Provider { } } +type CreateResourceIdFunc func(context.Context, types.Object) ([]byte, error) + +type resourceFuncs struct { + CreateImportId CreateResourceIdFunc +} + +var resourceFuncMap = map[string]resourceFuncs{ +{{- RenderResourceFuncMap }} +} + {{- /* Done */ -}}` + +const resourceFuncMapTmpl = ` +{{- range .Entries }} + "{{ .Key }}": resourceFuncs{ + CreateImportId: {{ .StructName }}ImportStateCreator, + }, +{{- end }} +` diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index 30100a1b..498066b8 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -3,6 +3,7 @@ package terraform_provider import ( "fmt" "log" + "log/slog" "strings" "text/template" @@ -11,6 +12,8 @@ import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) +var _ = slog.Debug + // NameProvider encapsulates naming conventions for Terraform resources. type NameProvider struct { TfName string @@ -83,12 +86,14 @@ func (g *GenerateTerraformProvider) updateProviderFile(renderedTemplate *strings // appendResourceType appends the given struct name to the appropriate slice in the TerraformProviderFile // based on the provided resourceType. func (g *GenerateTerraformProvider) appendResourceType(terraformProvider *properties.TerraformProviderFile, resourceType string, names *NameProvider) error { + slog.Warn("KK: HERE3-1", "len(Resources)", len(terraformProvider.Resources), "len(Names)", len(terraformProvider.Names)) switch resourceType { case "DataSource", "DataSourceList": terraformProvider.DataSources = append(terraformProvider.DataSources, names.DataSourceStructName) case "Resource": terraformProvider.Resources = append(terraformProvider.Resources, names.ResourceStructName) } + terraformProvider.Names[names.MetaName] = names.StructName return nil } @@ -163,6 +168,15 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper "ResourceDeleteFunction": func(structName string, serviceName string) (string, error) { return ResourceDeleteFunction(resourceTyp, names, serviceName, spec, names.PackageName) }, + "RenderImportStateStructs": func() (string, error) { + return RenderImportStateStructs(resourceTyp, names, spec) + }, + "RenderImportStateCreator": func() (string, error) { + return RenderImportStateCreator(resourceTyp, names, spec) + }, + "ResourceImportStateFunction": func() (string, error) { + return ResourceImportStateFunction(resourceTyp, names, spec) + }, "ParamToModelResource": ParamToModelResource, "ModelNestedStruct": ModelNestedStruct, "ResourceParamToSchema": func(paramName string, paramParameters interface{}) (string, error) { @@ -208,7 +222,6 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper conditionallyAddModifiers(terraformProvider.ImportManager, spec) conditionallyAddDefaults(terraformProvider.ImportManager, spec.Spec) - terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/path", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/diag", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/attr", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource", "") @@ -240,7 +253,6 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper conditionallyAddModifiers(terraformProvider.ImportManager, spec) conditionallyAddDefaults(terraformProvider.ImportManager, spec.Spec) - terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/path", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/diag", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/attr", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource", "") @@ -335,13 +347,18 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop } func (g *GenerateTerraformProvider) GenerateCommonCode(resourceTyp properties.ResourceType, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile) error { + terraformProvider.ImportManager.AddStandardImport("encoding/json", "") + terraformProvider.ImportManager.AddStandardImport("encoding/base64", "") + terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/types/basetypes", "") + terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/path", "") names := NewNameProvider(spec, resourceTyp) funcMap := template.FuncMap{ "RenderLocationStructs": func() (string, error) { return RenderLocationStructs(resourceTyp, names, spec) }, "RenderLocationSchemaGetter": func() (string, error) { return RenderLocationSchemaGetter(names, spec, terraformProvider.ImportManager) }, - "RenderCustomCommonCode": func() string { return RenderCustomCommonCode(names, spec) }, + "RenderLocationMarshallers": func() (string, error) { return RenderLocationMarshallers(names, spec) }, + "RenderCustomCommonCode": func() string { return RenderCustomCommonCode(names, spec) }, } return g.generateTerraformEntityTemplate("Common", names, spec, terraformProvider, commonTemplate, funcMap) } @@ -371,10 +388,11 @@ func (g *GenerateTerraformProvider) GenerateTerraformProvider(terraformProvider terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-log/tflog", "") funcMap := template.FuncMap{ - "RenderImports": func() (string, error) { return terraformProvider.ImportManager.RenderImports() }, - "DataSources": func() []string { return terraformProvider.DataSources }, - "Resources": func() []string { return terraformProvider.Resources }, - "ProviderParams": func() map[string]properties.TerraformProviderParams { return providerConfig.TerraformProviderParams }, + "RenderImports": func() (string, error) { return terraformProvider.ImportManager.RenderImports() }, + "DataSources": func() []string { return terraformProvider.DataSources }, + "Resources": func() []string { return terraformProvider.Resources }, + "RenderResourceFuncMap": func() (string, error) { return RenderResourceFuncMap(terraformProvider.Names) }, + "ProviderParams": func() map[string]properties.TerraformProviderParams { return providerConfig.TerraformProviderParams }, "ParamToModelBasic": func(paramName string, paramProp properties.TerraformProviderParams) (string, error) { return ParamToModelBasic(paramName, paramProp) },