From c4243bd11db12d45127fb47884bef2bb79235aa1 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Thu, 14 Nov 2024 14:17:50 +0100 Subject: [PATCH] Implement resource importing without Tfid move schemaType type to properties package Support for importing list resources --- .../provider/func_create_import_id.go | 80 ++++ assets/terraform/internal/provider/tfid.go | 174 -------- pkg/commands/codegen/codegen.go | 15 +- pkg/generate/generator.go | 14 +- pkg/properties/provider_file.go | 51 +++ pkg/properties/resourcetype.go | 9 + .../device_group_parent_crud.go | 1 - pkg/translate/terraform_provider/funcs.go | 389 ++++++++++++++---- pkg/translate/terraform_provider/template.go | 307 +++++++++----- .../terraform_provider_file.go | 139 ++++--- .../terraform_provider_file_test.go | 14 +- 11 files changed, 754 insertions(+), 439 deletions(-) create mode 100644 assets/terraform/internal/provider/func_create_import_id.go delete mode 100644 assets/terraform/internal/provider/tfid.go diff --git a/assets/terraform/internal/provider/func_create_import_id.go b/assets/terraform/internal/provider/func_create_import_id.go new file mode 100644 index 00000000..36f0092f --- /dev/null +++ b/assets/terraform/internal/provider/func_create_import_id.go @@ -0,0 +1,80 @@ +package provider + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ function.Function = &ImportStateCreator{} +) + +type ImportStateCreator struct{} + +func NewCreateImportIdFunction() function.Function { + return &ImportStateCreator{} +} + +func (o *ImportStateCreator) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "generate_import_id" +} + +func (o *ImportStateCreator) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Generate Import ID", + Description: "Generate Import ID for the given resource that can be used to import resources into the state.", + + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "resource_asn", + Description: "Name of the resource", + }, + function.DynamicParameter{ + Name: "resource_data", + Description: "Resource data", + }, + }, + Return: function.StringReturn{}, + } +} + +func (o *ImportStateCreator) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var resourceAsn string + var dynamicResource types.Dynamic + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &resourceAsn, &dynamicResource)) + if resp.Error != nil { + return + } + + var resource types.Object + switch value := dynamicResource.UnderlyingValue().(type) { + case types.Object: + resource = value + default: + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(1, fmt.Sprintf("Wrong resource type: must be an object"))) + return + } + + var data []byte + + if resourceFuncs, found := resourceFuncMap[resourceAsn]; !found { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, fmt.Sprintf("Unsupported resource type: %s'", resourceAsn))) + return + } else { + var err error + data, err = resourceFuncs.CreateImportId(ctx, resource) + if err != nil { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(err.Error())) + return + } + + } + + result := base64.StdEncoding.EncodeToString(data) + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, result)) +} 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..7f865241 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 + specMetadata := make(map[string]properties.TerraformProviderSpecMetadata) for _, specPath := range c.specs { log.Printf("Parsing %s...\n", specPath) @@ -126,13 +127,18 @@ 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 { + specMetadata[k] = v + } + } if pluralVariant { @@ -149,13 +155,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 { + specMetadata[k] = v + } } } else if c.commandType == properties.CommandTypeSDK && !spec.GoSdkSkip { generator := generate.NewCreator(config.Output.GoSdk, c.templatePath, spec) @@ -173,6 +183,7 @@ func (c *Command) Execute() error { newProviderObject := properties.NewTerraformProviderFile(providerSpec.Name) newProviderObject.DataSources = append(newProviderObject.DataSources, dataSourceList...) newProviderObject.Resources = append(newProviderObject.Resources, resourceList...) + newProviderObject.SpecMetadata = specMetadata 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 e1a118d9..3c4e907d 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]properties.TerraformProviderSpecMetadata, 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.SpecMetadata, 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..16cfc95f 100644 --- a/pkg/properties/provider_file.go +++ b/pkg/properties/provider_file.go @@ -1,9 +1,11 @@ package properties import ( + "fmt" "strings" "github.com/paloaltonetworks/pan-os-codegen/pkg/imports" + "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" ) // NewTerraformProviderFile returns a new handler for a file that the @@ -19,10 +21,58 @@ func NewTerraformProviderFile(filename string) *TerraformProviderFile { ImportManager: imports.NewManager(), DataSources: make([]string, 0, 10), Resources: make([]string, 0, 10), + SpecMetadata: make(map[string]TerraformProviderSpecMetadata), Code: &code, } } +type TerraformNameProvider struct { + TfName string + MetaName string + StructName string + DataSourceStructName string + ResourceStructName string + PackageName string +} + +func NewTerraformNameProvider(spec *Normalization, resourceTyp ResourceType) *TerraformNameProvider { + var tfName string + switch resourceTyp { + case ResourceEntry, ResourceCustom: + tfName = spec.TerraformProviderConfig.Suffix + case ResourceEntryPlural: + tfName = spec.TerraformProviderConfig.PluralSuffix + case ResourceUuid: + tfName = spec.TerraformProviderConfig.Suffix + case ResourceUuidPlural: + suffix := spec.TerraformProviderConfig.Suffix + pluralName := spec.TerraformProviderConfig.PluralName + tfName = fmt.Sprintf("%s_%s", suffix, pluralName) + } + objectName := tfName + + metaName := fmt.Sprintf("_%s", naming.Underscore("", strings.ToLower(objectName), "")) + structName := naming.CamelCase("", tfName, "", true) + dataSourceStructName := naming.CamelCase("", tfName, "DataSource", true) + resourceStructName := naming.CamelCase("", tfName, "Resource", true) + packageName := spec.GoSdkPath[len(spec.GoSdkPath)-1] + return &TerraformNameProvider{tfName, metaName, structName, dataSourceStructName, resourceStructName, packageName} +} + +type TerraformSpecFlags uint + +const ( + TerraformSpecDatasource = 0x01 + TerraformSpecResource = 0x02 + TerraformSpecImportable = 0x04 +) + +type TerraformProviderSpecMetadata struct { + ResourceSuffix string + StructName string + Flags TerraformSpecFlags +} + // TerraformProviderFile is a Terraform provider file handler. type TerraformProviderFile struct { Filename string @@ -30,5 +80,6 @@ type TerraformProviderFile struct { ImportManager *imports.Manager DataSources []string Resources []string + SpecMetadata map[string]TerraformProviderSpecMetadata Code *strings.Builder } diff --git a/pkg/properties/resourcetype.go b/pkg/properties/resourcetype.go index ab70f5cf..7206613b 100644 --- a/pkg/properties/resourcetype.go +++ b/pkg/properties/resourcetype.go @@ -9,3 +9,12 @@ const ( ResourceUuid ResourceType = iota ResourceUuidPlural ResourceType = iota ) + +type SchemaType int + +const ( + SchemaResource SchemaType = iota + SchemaDataSource SchemaType = iota + SchemaCommon SchemaType = iota + SchemaProvider SchemaType = iota +) 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 822423f0..349cd466 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -912,6 +912,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 @@ -923,7 +1012,7 @@ func RenderCustomCommonCode(names *NameProvider, spec *properties.Normalization) } -func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager, structPrefix string, packageName string, param *properties.SpecParam, validators *validatorCtx) []schemaCtx { +func createSchemaSpecForParameter(schemaTyp properties.SchemaType, manager *imports.Manager, structPrefix string, packageName string, param *properties.SpecParam, validators *validatorCtx) []schemaCtx { var schemas []schemaCtx if param.Spec == nil { @@ -992,7 +1081,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager } var validators *validatorCtx - if idx == 0 { + if schemaTyp == properties.SchemaResource && idx == 0 { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") @@ -1007,20 +1096,21 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager } var isResource bool - if schemaTyp == schemaResource { + if schemaTyp == properties.SchemaResource { isResource = true } var computed, required bool switch schemaTyp { - case schemaDataSource: + case properties.SchemaDataSource: computed = true required = false - case schemaResource: - required = param.Required + case properties.SchemaResource: if param.TerraformProviderConfig != nil { computed = param.TerraformProviderConfig.Computed } + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } schemas = append(schemas, schemaCtx{ @@ -1068,7 +1158,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager return schemas } -func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Manager, packageName string, param *properties.SpecParam, validators *validatorCtx) attributeCtx { +func createSchemaAttributeForParameter(schemaTyp properties.SchemaType, manager *imports.Manager, packageName string, param *properties.SpecParam, validators *validatorCtx) attributeCtx { var schemaType, elementType string switch param.Type { case "": @@ -1089,7 +1179,7 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Ma } var defaultValue *defaultCtx - if schemaTyp == schemaResource && param.Default != "" { + if schemaTyp == properties.SchemaResource && param.Default != "" { var value string switch param.Type { case "string": @@ -1105,16 +1195,17 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Ma var computed, required bool switch schemaTyp { - case schemaDataSource: + case properties.SchemaDataSource: required = false computed = true - case schemaResource: - required = param.Required + case properties.SchemaResource: if param.TerraformProviderConfig != nil { computed = param.TerraformProviderConfig.Computed } else if param.Default != "" { computed = true } + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } return attributeCtx{ @@ -1132,15 +1223,8 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Ma } } -type schemaType int - -const ( - schemaDataSource schemaType = iota - schemaResource -) - // createSchemaSpecForUuidModel creates a schema for uuid-type resources, where top-level model describes a list of objects. -func createSchemaSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { +func createSchemaSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { var schemas []schemaCtx var attributes []attributeCtx @@ -1189,7 +1273,7 @@ func createSchemaSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp }) var isResource bool - if schemaTyp == schemaResource { + if schemaTyp == properties.SchemaResource { isResource = true } schemas = append(schemas, schemaCtx{ @@ -1221,7 +1305,7 @@ func createSchemaSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp // createSchemaSpecForEntrySingularModel creates a schema for entry-type singular resources. // // Entry-type singular resources are resources that manage a single object in PAN-OS, e.g. `resource_ethernet_interface`. -func createSchemaSpecForEntrySingularModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { +func createSchemaSpecForEntrySingularModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { var schemas []schemaCtx var attributes []attributeCtx location := &properties.NameVariant{ @@ -1237,25 +1321,11 @@ 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...) var isResource bool - if schemaTyp == schemaResource { + if schemaTyp == properties.SchemaResource { isResource = true } schemas = append(schemas, schemaCtx{ @@ -1279,7 +1349,7 @@ func createSchemaSpecForEntrySingularModel(resourceTyp properties.ResourceType, // provide users with a simple way of indexing into specific objects based on their name, // so the terraform object represents lists as sets, where key is object name, and the value // is an terraform nested attribute describing the rest of object parameters. -func createSchemaSpecForEntryListModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { +func createSchemaSpecForEntryListModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { var schemas []schemaCtx var attributes []attributeCtx location := &properties.NameVariant{ @@ -1311,7 +1381,7 @@ func createSchemaSpecForEntryListModel(resourceTyp properties.ResourceType, sche }) var isResource bool - if schemaTyp == schemaResource { + if schemaTyp == properties.SchemaResource { isResource = true } schemas = append(schemas, schemaCtx{ @@ -1341,13 +1411,15 @@ func createSchemaSpecForEntryListModel(resourceTyp properties.ResourceType, sche } // createSchemaSpecForModel generates schema spec for the top-level object based on the ResourceType. -func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, manager *imports.Manager) []schemaCtx { +func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, manager *imports.Manager) []schemaCtx { var packageName string switch schemaTyp { - case schemaDataSource: + case properties.SchemaDataSource: packageName = "dsschema" - case schemaResource: + case properties.SchemaResource: packageName = "rsschema" + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } if spec.Spec == nil { @@ -1358,10 +1430,12 @@ func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp sch var structName string switch schemaTyp { - case schemaDataSource: + case properties.SchemaDataSource: structName = names.DataSourceStructName - case schemaResource: + case properties.SchemaResource: structName = names.ResourceStructName + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } switch resourceTyp { @@ -1376,7 +1450,7 @@ func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp sch } } -func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) ([]attributeCtx, []schemaCtx) { +func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) ([]attributeCtx, []schemaCtx) { var schemas []schemaCtx var attributes []attributeCtx @@ -1452,7 +1526,7 @@ func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schem continue } var validators *validatorCtx - if idx == 0 { + if schemaTyp == properties.SchemaResource && idx == 0 { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") @@ -1649,7 +1723,7 @@ func RenderResourceSchema(resourceTyp properties.ResourceType, names *NameProvid } data := context{ - Schemas: createSchemaSpecForModel(resourceTyp, schemaResource, spec, manager), + Schemas: createSchemaSpecForModel(resourceTyp, properties.SchemaResource, spec, manager), } return processTemplate(renderSchemaTemplate, "render-resource-schema", data, commonFuncMap) @@ -1661,23 +1735,22 @@ func RenderDataSourceSchema(resourceTyp properties.ResourceType, names *NameProv } data := context{ - Schemas: createSchemaSpecForModel(resourceTyp, schemaDataSource, spec, manager), + Schemas: createSchemaSpecForModel(resourceTyp, properties.SchemaDataSource, spec, manager), } return processTemplate(renderSchemaTemplate, "render-resource-schema", data, commonFuncMap) } 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 }} @@ -1685,7 +1758,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 } @@ -1743,14 +1816,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, } @@ -2001,7 +2076,7 @@ func dataSourceStructContextForParam(structPrefix string, param *properties.Spec return structs } -func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { +func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { var structs []datasourceStructSpec var fields []datasourceStructFieldSpec @@ -2028,10 +2103,12 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp var structName string switch schemaTyp { - case schemaResource: + case properties.SchemaResource: structName = names.ResourceStructName - case schemaDataSource: + case properties.SchemaDataSource: structName = names.DataSourceStructName + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } listNameStr := spec.TerraformProviderConfig.PluralName @@ -2068,7 +2145,7 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp return structs } -func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { +func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { var structs []datasourceStructSpec var fields []datasourceStructFieldSpec @@ -2080,10 +2157,12 @@ func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, sche var structName string switch schemaTyp { - case schemaResource: + case properties.SchemaResource: structName = names.ResourceStructName - case schemaDataSource: + case properties.SchemaDataSource: structName = names.DataSourceStructName + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } listNameStr := spec.TerraformProviderConfig.PluralName @@ -2120,17 +2199,11 @@ func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, sche return structs } -func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { +func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { var structs []datasourceStructSpec 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), @@ -2139,10 +2212,12 @@ func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTy var structName string switch schemaTyp { - case schemaDataSource: + case properties.SchemaDataSource: structName = names.DataSourceStructName - case schemaResource: + case properties.SchemaResource: structName = names.ResourceStructName + case properties.SchemaCommon, properties.SchemaProvider: + panic("unreachable") } normalizationFields, normalizationStructs := createStructSpecForNormalization(resourceTyp, structName, spec) @@ -2159,7 +2234,7 @@ func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTy return structs } -func createStructSpecForModel(resourceTyp properties.ResourceType, schemaTyp schemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { +func createStructSpecForModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { if spec.Spec == nil { return nil } @@ -2229,7 +2304,7 @@ func RenderResourceStructs(resourceTyp properties.ResourceType, names *NameProvi } data := context{ - Structs: createStructSpecForModel(resourceTyp, schemaResource, spec, names), + Structs: createStructSpecForModel(resourceTyp, properties.SchemaResource, spec, names), } return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap) @@ -2241,7 +2316,7 @@ func RenderDataSourceStructs(resourceTyp properties.ResourceType, names *NamePro } data := context{ - Structs: createStructSpecForModel(resourceTyp, schemaDataSource, spec, names), + Structs: createStructSpecForModel(resourceTyp, properties.SchemaDataSource, spec, names), } return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap) @@ -2262,8 +2337,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) @@ -2584,8 +2659,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) @@ -2595,6 +2670,136 @@ 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: + fields = append(fields, importStateStructFieldSpec{ + Name: "Names", + Type: "[]string", + Tags: "`json:\"names\"`", + }) + case properties.ResourceUuidPlural: + fields = append(fields, []importStateStructFieldSpec{ + { + Name: "Names", + Type: "[]string", + Tags: "`json:\"names\"`", + }, + { + Name: "Position", + Type: "TerraformPositionObject", + Tags: "`json:\"position\"`", + }, + }...) + case properties.ResourceCustom: + panic("unreachable") + } + + 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) { + // Only singular entries can be imported at the time + if resourceTyp != properties.ResourceEntry { + return "", nil + } + + 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) { + // Only singular entries can be imported at the time + if resourceTyp != properties.ResourceEntry { + return "", nil + } + + 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) + case properties.ResourceCustom: + panic("unreachable") + } + + return processTemplate(resourceImportStateFunctionTmpl, "resource-import-state-function", data, nil) +} + +func RenderImportStateCreator(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization) (string, error) { + if resourceTyp != properties.ResourceEntry { + return "", nil + } + + type context struct { + FuncName string + ModelName string + StructNamePrefix string + } + + data := context{ + FuncName: fmt.Sprintf("%sImportStateCreator", names.StructName), + ModelName: fmt.Sprintf("%sModel", names.ResourceStructName), + StructNamePrefix: names.StructName, + } + + return processTemplate(resourceImportStateCreatorTmpl, "render-import-state-creator", data, commonFuncMap) +} + func ConfigEntry(entryName string, param *properties.SpecParam) (string, error) { var entries []Entry @@ -2617,6 +2822,38 @@ func ConfigEntry(entryName string, param *properties.SpecParam) (string, error) return processTemplate(resourceConfigEntry, "config-entry", entryData, nil) } +func RenderResourceFuncMap(names map[string]properties.TerraformProviderSpecMetadata) (string, error) { + type entry struct { + Key string + StructName string + } + + type context struct { + Entries []entry + } + + var entries []entry + for key, metadata := range names { + if key == "" { + continue + } + + if metadata.Flags&properties.TerraformSpecImportable == 0 { + continue + } + + entries = append(entries, entry{ + Key: fmt.Sprintf("panos%s", key), + StructName: metadata.StructName, + }) + } + 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 39ece3f3..13c5b580 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,13 @@ var ( ) func New{{ resourceStructName }}() resource.Resource { +{{- if IsImportable }} + if _, found := resourceFuncMap["panos{{ metaName }}"]; !found { + resourceFuncMap["panos{{ metaName }}"] = resourceFuncs{ + CreateImportId: {{ structName }}ImportStateCreator, + } + } +{{- end }} return &{{ resourceStructName }}{} } @@ -260,21 +263,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 +356,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 +584,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 +613,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 +843,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 +861,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 +895,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 +1111,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 +1127,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 +1143,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 +1160,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 +1236,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 +1251,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 +1278,152 @@ 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) { + attrs := resource.Attributes() + if attrs == nil { + return nil, fmt.Errorf("Object has no attributes") + } + + locationAttr, ok := attrs["location"] + if !ok { + return nil, fmt.Errorf("location attribute missing") + } + + var location {{ .StructNamePrefix }}Location + switch value := locationAttr.(type) { + case types.Object: + value.As(ctx, &location, basetypes.ObjectAsOptions{}) + default: + return nil, fmt.Errorf("location attribute expected to be an object") + } + + nameAttr, ok := attrs["name"] + if !ok { + return nil, fmt.Errorf("name attribute missing") + } + + var name string + switch value := nameAttr.(type) { + case types.String: + name = value.ValueString() + default: + return nil, fmt.Errorf("name attribute expected to be a string") + } + + importStruct := {{ .StructNamePrefix }}ImportState{ + Location: location, + Name: name, + } + + 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)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), obj.Name)...) +` + const commonTemplate = ` {{- RenderLocationStructs }} {{- RenderLocationSchemaGetter }} +{{- RenderLocationMarshallers }} + {{- RenderCustomCommonCode }} ` @@ -1360,7 +1437,7 @@ var ( ) func New{{ dataSourceStructName }}() datasource.DataSource { - return &{{ dataSourceStructName }}{} + return &{{ dataSourceStructName }}{} } type {{ dataSourceStructName }} struct { @@ -1383,22 +1460,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 }} @@ -1609,7 +1670,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 }} @@ -1628,6 +1688,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, } } @@ -1640,4 +1701,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 3166cf98..d32678cf 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -3,47 +3,21 @@ package terraform_provider import ( "fmt" "log" + "log/slog" "strings" "text/template" "github.com/paloaltonetworks/pan-os-codegen/pkg/imports" - "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) -// NameProvider encapsulates naming conventions for Terraform resources. -type NameProvider struct { - TfName string - MetaName string - StructName string - DataSourceStructName string - ResourceStructName string - PackageName string -} +var _ = slog.Debug + +type NameProvider = properties.TerraformNameProvider // NewNameProvider creates a new NameProvider based on given specifications. func NewNameProvider(spec *properties.Normalization, resourceTyp properties.ResourceType) *NameProvider { - var tfName string - switch resourceTyp { - case properties.ResourceEntry, properties.ResourceCustom: - tfName = spec.TerraformProviderConfig.Suffix - case properties.ResourceEntryPlural: - tfName = spec.TerraformProviderConfig.PluralSuffix - case properties.ResourceUuid: - tfName = spec.TerraformProviderConfig.Suffix - case properties.ResourceUuidPlural: - suffix := spec.TerraformProviderConfig.Suffix - pluralName := spec.TerraformProviderConfig.PluralName - tfName = fmt.Sprintf("%s_%s", suffix, pluralName) - } - objectName := tfName - - metaName := fmt.Sprintf("_%s", naming.Underscore("", strings.ToLower(objectName), "")) - structName := naming.CamelCase("", tfName, "", true) - dataSourceStructName := naming.CamelCase("", tfName, "DataSource", true) - resourceStructName := naming.CamelCase("", tfName, "Resource", true) - packageName := spec.GoSdkPath[len(spec.GoSdkPath)-1] - return &NameProvider{tfName, metaName, structName, dataSourceStructName, resourceStructName, packageName} + return properties.NewTerraformNameProvider(spec, resourceTyp) } // GenerateTerraformProvider handles the generation of Terraform resources and data sources. @@ -56,58 +30,85 @@ func (g *GenerateTerraformProvider) createTemplate(resourceType string, spec *pr } // executeTemplate executes the provided resource template using the given spec and returns an error if it fails. -func (g *GenerateTerraformProvider) executeTemplate(template *template.Template, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceType string, names *NameProvider) error { +func (g *GenerateTerraformProvider) executeTemplate(template *template.Template, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider) error { var renderedTemplate strings.Builder if err := template.Execute(&renderedTemplate, spec); err != nil { - return fmt.Errorf("error executing %s template: %v", resourceType, err) + return fmt.Errorf("error executing %v template: %v", resourceTyp, err) } renderedTemplate.WriteString("\n") - return g.updateProviderFile(&renderedTemplate, terraformProvider, resourceType, names) + return g.updateProviderFile(&renderedTemplate, terraformProvider, resourceTyp, schemaTyp, names) } // updateProviderFile updates the Terraform provider file by appending the rendered template // to the appropriate slice in the TerraformProviderFile based on the provided resourceType. -func (g *GenerateTerraformProvider) updateProviderFile(renderedTemplate *strings.Builder, terraformProvider *properties.TerraformProviderFile, resourceType string, names *NameProvider) error { - switch resourceType { - case "ProviderFile": +func (g *GenerateTerraformProvider) updateProviderFile(renderedTemplate *strings.Builder, terraformProvider *properties.TerraformProviderFile, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider) error { + if schemaTyp == properties.SchemaProvider { terraformProvider.Code = renderedTemplate - default: + } else { log.Printf("updateProviderFile() renderedTemplate: %d", renderedTemplate.Len()) if _, err := terraformProvider.Code.WriteString(renderedTemplate.String()); err != nil { - return fmt.Errorf("error writing %s template: %v", resourceType, err) + return fmt.Errorf("error writing %v template: %v", resourceTyp, err) } } - return g.appendResourceType(terraformProvider, resourceType, names) + return g.appendResourceType(terraformProvider, resourceTyp, schemaTyp, names) } // 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 { - switch resourceType { - case "DataSource", "DataSourceList": +func (g *GenerateTerraformProvider) appendResourceType(terraformProvider *properties.TerraformProviderFile, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider) error { + var flags properties.TerraformSpecFlags + switch schemaTyp { + case properties.SchemaDataSource: + flags |= properties.TerraformSpecDatasource terraformProvider.DataSources = append(terraformProvider.DataSources, names.DataSourceStructName) - case "Resource": + case properties.SchemaResource: + flags |= properties.TerraformSpecResource terraformProvider.Resources = append(terraformProvider.Resources, names.ResourceStructName) + case properties.SchemaProvider, properties.SchemaCommon: + } + + switch resourceTyp { + case properties.ResourceEntry: + flags |= properties.TerraformSpecImportable + case properties.ResourceCustom, properties.ResourceEntryPlural, properties.ResourceUuid, properties.ResourceUuidPlural: + } + + terraformProvider.SpecMetadata[names.MetaName] = properties.TerraformProviderSpecMetadata{ + ResourceSuffix: names.MetaName, + StructName: names.StructName, + Flags: flags, } return nil } // generateTerraformEntityTemplate is the common logic for generating Terraform resources and data sources. -func (g *GenerateTerraformProvider) generateTerraformEntityTemplate(resourceType string, names *NameProvider, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, templateStr string, funcMap template.FuncMap) error { +func (g *GenerateTerraformProvider) generateTerraformEntityTemplate(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, templateStr string, funcMap template.FuncMap) error { if templateStr == "" { return nil } + + var resourceType string + switch schemaTyp { + case properties.SchemaDataSource: + resourceType = "DataSource" + case properties.SchemaResource: + resourceType = "Resource" + case properties.SchemaCommon: + resourceType = "Common" + case properties.SchemaProvider: + resourceType = "ProviderFile" + } + template, err := g.createTemplate(resourceType, spec, templateStr, funcMap) if err != nil { log.Fatalf("Error creating template: %v", err) return err } - return g.executeTemplate(template, spec, terraformProvider, resourceType, names) + return g.executeTemplate(template, spec, terraformProvider, resourceTyp, schemaTyp, names) } // GenerateTerraformResource generates a Terraform resource template. func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp properties.ResourceType, spec *properties.Normalization, terraformProvider *properties.TerraformProviderFile) error { - resourceType := "Resource" names := NewNameProvider(spec, resourceTyp) var structType string @@ -132,6 +133,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper "IsCustom": func() bool { return spec.TerraformProviderConfig.ResourceType == properties.TerraformResourceCustom }, "IsUuid": func() bool { return spec.HasEntryUuid() }, "IsConfig": func() bool { return !spec.HasEntryName() && !spec.HasEntryUuid() }, + "IsImportable": func() bool { return resourceTyp == properties.ResourceEntry }, "resourceSDKName": func() string { return names.PackageName }, "HasPosition": func() bool { return hasPosition }, "metaName": func() string { return names.MetaName }, @@ -163,6 +165,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 +219,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", "") @@ -217,7 +227,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/types", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-log/tflog", "") - err := g.generateTerraformEntityTemplate(resourceType, names, spec, terraformProvider, resourceObj, funcMap) + err := g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaResource, names, spec, terraformProvider, resourceObj, funcMap) if err != nil { return err } @@ -240,7 +250,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", "") @@ -249,7 +258,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/types", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-log/tflog", "") - err := g.generateTerraformEntityTemplate(resourceType, names, spec, terraformProvider, resourceObj, funcMap) + err := g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaResource, names, spec, terraformProvider, resourceObj, funcMap) if err != nil { return err } @@ -280,7 +289,6 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource/schema", "rsschema") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault", "") - resourceType := "DataSource" names := NewNameProvider(spec, resourceTyp) funcMap := template.FuncMap{ "GoSDKSkipped": func() bool { return spec.GoSdkSkip }, @@ -309,7 +317,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop return RenderDataSourceSchema(resourceTyp, names, spec, terraformProvider.ImportManager) }, } - err := g.generateTerraformEntityTemplate(resourceType, names, spec, terraformProvider, dataSourceSingletonObj, funcMap) + err := g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaDataSource, names, spec, terraformProvider, dataSourceSingletonObj, funcMap) if err != nil { return err } @@ -335,15 +343,23 @@ 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", "") + // Imports required by resources that can be imported into state + if resourceTyp == properties.ResourceEntry { + 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) + return g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaCommon, names, spec, terraformProvider, commonTemplate, funcMap) } // GenerateTerraformProviderFile generates the entire provider file. @@ -356,7 +372,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformProviderFile(spec *properti "renderCustomImports": func() string { return RenderCustomImports(spec) }, "renderCode": func() string { return terraformProvider.Code.String() }, } - return g.generateTerraformEntityTemplate("ProviderFile", &NameProvider{}, spec, terraformProvider, providerFile, funcMap) + return g.generateTerraformEntityTemplate(properties.ResourceCustom, properties.SchemaProvider, &NameProvider{}, spec, terraformProvider, providerFile, funcMap) } func (g *GenerateTerraformProvider) GenerateTerraformProvider(terraformProvider *properties.TerraformProviderFile, spec *properties.Normalization, providerConfig properties.TerraformProvider) error { @@ -374,10 +390,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.SpecMetadata) }, + "ProviderParams": func() map[string]properties.TerraformProviderParams { return providerConfig.TerraformProviderParams }, "ParamToModelBasic": func(paramName string, paramProp properties.TerraformProviderParams) (string, error) { return ParamToModelBasic(paramName, paramProp) }, @@ -385,7 +402,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformProvider(terraformProvider return ParamToSchemaProvider(paramName, paramProp) }, } - return g.generateTerraformEntityTemplate("ProviderFile", &NameProvider{}, spec, terraformProvider, provider, funcMap) + return g.generateTerraformEntityTemplate(properties.ResourceCustom, properties.SchemaProvider, &NameProvider{}, spec, terraformProvider, provider, funcMap) } func sdkPkgPath(spec *properties.Normalization) string { diff --git a/pkg/translate/terraform_provider/terraform_provider_file_test.go b/pkg/translate/terraform_provider/terraform_provider_file_test.go index 561f4f92..6bbd2fef 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file_test.go +++ b/pkg/translate/terraform_provider/terraform_provider_file_test.go @@ -34,11 +34,14 @@ func TestExecuteTemplate(t *testing.T) { g := &GenerateTerraformProvider{} tmpl, _ := template.New("test").Parse("Name: {{.Name}}") spec := &properties.Normalization{Name: "testResource"} - terraformProvider := &properties.TerraformProviderFile{Code: new(strings.Builder)} + terraformProvider := &properties.TerraformProviderFile{ + SpecMetadata: make(map[string]properties.TerraformProviderSpecMetadata), + Code: new(strings.Builder), + } names := &NameProvider{TfName: "testResource", MetaName: "_testResource", ResourceStructName: "TestResource"} // When - err := g.executeTemplate(tmpl, spec, terraformProvider, "Resource", names) + err := g.executeTemplate(tmpl, spec, terraformProvider, properties.ResourceEntry, properties.SchemaResource, names) // Then assert.NoError(t, err, "executeTemplate should not return an error") @@ -50,12 +53,15 @@ func TestGenerateTerraformEntityTemplate(t *testing.T) { g := &GenerateTerraformProvider{} names := &NameProvider{TfName: "testResource", MetaName: "_testResource", ResourceStructName: "TestResource"} spec := &properties.Normalization{Name: "testResource"} - terraformProvider := &properties.TerraformProviderFile{Code: new(strings.Builder)} + terraformProvider := &properties.TerraformProviderFile{ + SpecMetadata: make(map[string]properties.TerraformProviderSpecMetadata), + Code: new(strings.Builder), + } templateStr := "Name: {{.Name}}" funcMap := template.FuncMap{"testFunc": func() string { return "test" }} // When - err := g.generateTerraformEntityTemplate("Resource", names, spec, terraformProvider, templateStr, funcMap) + err := g.generateTerraformEntityTemplate(properties.ResourceEntry, properties.SchemaResource, names, spec, terraformProvider, templateStr, funcMap) // Then assert.NoError(t, err, "generateTerraformEntityTemplate should not return an error")