diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 1833570a..e396593d 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -224,8 +224,6 @@ func (c *Creator) createFullFilePath(templateName string) string { } // listOfTemplates returns a list of templates defined in TemplatesDir. -// Excludes templates in the "partials" subdirectory as those are meant to be -// called from within other templates, not processed independently. func (c *Creator) listOfTemplates() ([]string, error) { var files []string err := filepath.WalkDir(c.TemplatesDir, func(path string, entry os.DirEntry, err error) error { diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 2de1bec0..6d572f07 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -65,6 +65,7 @@ type TerraformProviderConfig struct { Action bool `json:"action" yaml:"action"` CustomValidation bool `json:"custom_validation" yaml:"custom_validation"` SkipResource bool `json:"skip_resource" yaml:"skip_resource"` + SkipListResource *bool `json:"skip_list_resource" yaml:"skip_list_resource"` SkipDatasource bool `json:"skip_datasource" yaml:"skip_datasource"` SkipDatasourceListing bool `json:"skip_datasource_listing" yaml:"skip_datasource_listing"` ResourceType TerraformResourceType `json:"resource_type" yaml:"resource_type"` @@ -702,6 +703,7 @@ func schemaToSpec(object object.Object) (*Normalization, error) { Action: object.TerraformConfig.Action, CustomValidation: object.TerraformConfig.CustomValidation, SkipResource: object.TerraformConfig.SkipResource, + SkipListResource: object.TerraformConfig.SkipListResource, SkipDatasource: object.TerraformConfig.SkipDatasource, SkipDatasourceListing: object.TerraformConfig.SkipdatasourceListing, ResourceType: TerraformResourceType(object.TerraformConfig.ResourceType), diff --git a/pkg/properties/provider_file.go b/pkg/properties/provider_file.go index c2b10b8b..d241326a 100644 --- a/pkg/properties/provider_file.go +++ b/pkg/properties/provider_file.go @@ -68,6 +68,10 @@ func (o TerraformNameProvider) IdentityModelStructName() string { return fmt.Sprintf("%sIdentityModel", o.ResourceStructName) } +func (o TerraformNameProvider) ListResourceStructName() string { + return fmt.Sprintf("%sListResource", o.StructName) +} + type TerraformSpecFlags uint const ( @@ -91,6 +95,7 @@ type TerraformProviderFile struct { DataSources []string Resources []string EphemeralResources []string + ListResources []string Actions []string SpecMetadata map[string]TerraformProviderSpecMetadata Code *strings.Builder diff --git a/pkg/properties/resourcetype.go b/pkg/properties/resourcetype.go index f6743e7e..f3f2afb0 100644 --- a/pkg/properties/resourcetype.go +++ b/pkg/properties/resourcetype.go @@ -16,8 +16,10 @@ type SchemaType string const ( SchemaResource SchemaType = "resource" SchemaEphemeralResource SchemaType = "ephemeral-resource" + SchemaListResource SchemaType = "list-resource" SchemaAction SchemaType = "action" SchemaDataSource SchemaType = "datasource" SchemaCommon SchemaType = "common" SchemaProvider SchemaType = "provider" + SchemaCustom SchemaType = "custom" ) diff --git a/pkg/schema/object/object.go b/pkg/schema/object/object.go index 0e6ccbda..f5f7b97c 100644 --- a/pkg/schema/object/object.go +++ b/pkg/schema/object/object.go @@ -43,6 +43,7 @@ type TerraformConfig struct { SkipResource bool `yaml:"skip_resource"` SkipDatasource bool `yaml:"skip_datasource"` SkipdatasourceListing bool `yaml:"skip_datasource_listing"` + SkipListResource *bool `yaml:"skip_list_resource"` ResourceType TerraformResourceType `yaml:"resource_type"` XmlNode *string `yaml:"xml_node"` CustomFunctions map[string]bool `yaml:"custom_functions"` diff --git a/pkg/translate/terraform_provider/conversion.go b/pkg/translate/terraform_provider/conversion.go index 4d95d5e6..e899ddd7 100644 --- a/pkg/translate/terraform_provider/conversion.go +++ b/pkg/translate/terraform_provider/conversion.go @@ -84,7 +84,7 @@ func renderSpecsForParams(names *NameProvider, schemaTyp properties.SchemaType, structPrefix = names.DataSourceStructName case properties.SchemaResource, properties.SchemaEphemeralResource: structPrefix = names.ResourceStructName - case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction: + case properties.SchemaListResource, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction, properties.SchemaCustom: panic(fmt.Sprintf("invalid schema type: %s", schemaTyp)) } diff --git a/pkg/translate/terraform_provider/crud_operations.go b/pkg/translate/terraform_provider/crud_operations.go index 0b7d23d7..a97ace55 100644 --- a/pkg/translate/terraform_provider/crud_operations.go +++ b/pkg/translate/terraform_provider/crud_operations.go @@ -35,7 +35,7 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv return RendeCreateUpdateMovementRequired(state, entries) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { - return RenderLocationsStateToPango(names, paramSpec, source, dest) + return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics") }, } @@ -151,7 +151,7 @@ func DataSourceReadFunction(resourceTyp properties.ResourceType, names *NameProv return RenderLocationsPangoToState(names, paramSpec, source, dest) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { - return RenderLocationsStateToPango(names, paramSpec, source, dest) + return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics") }, } @@ -219,7 +219,7 @@ func ResourceReadFunction(resourceTyp properties.ResourceType, names *NameProvid return RenderLocationsPangoToState(names, paramSpec, source, dest) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { - return RenderLocationsStateToPango(names, paramSpec, source, dest) + return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics") }, } @@ -281,7 +281,7 @@ func ResourceUpdateFunction(resourceTyp properties.ResourceType, names *NameProv return RendeCreateUpdateMovementRequired(state, entries) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { - return RenderLocationsStateToPango(names, paramSpec, source, dest) + return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics") }, "RenderLocationsPangoToState": func(source string, dest string) (string, error) { return RenderLocationsPangoToState(names, paramSpec, source, dest) @@ -345,7 +345,7 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv return RenderImportLocationAssignment(names, paramSpec, source, dest) }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { - return RenderLocationsStateToPango(names, paramSpec, source, dest) + return RenderLocationsStateToPango(names, paramSpec, source, dest, "resp.Diagnostics") }, } diff --git a/pkg/translate/terraform_provider/entity_generators.go b/pkg/translate/terraform_provider/entity_generators.go index 8f0221b0..036b58c6 100644 --- a/pkg/translate/terraform_provider/entity_generators.go +++ b/pkg/translate/terraform_provider/entity_generators.go @@ -91,6 +91,15 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper "RenderXpathComponentsGetter": func() (string, error) { return RenderXpathComponentsGetter(names.ResourceStructName, spec) }, + "RenderMainStruct": func() (string, error) { + return RenderMainStruct(resourceTyp, schemaTyp, names, spec) + }, + "RenderConfigureFunc": func() (string, error) { + return RenderConfigureFunc(resourceTyp, schemaTyp, names, spec) + }, + "RenderModelStructs": func() (string, error) { + return RenderModelStructs(resourceTyp, schemaTyp, names, spec) + }, "ResourceCreateFunction": func(structName string, serviceName string) (string, error) { return ResourceCreateFunction(resourceTyp, names, serviceName, spec, terraformProvider, names.PackageName) }, @@ -265,6 +274,37 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper return nil } +// GenerateTerraformListResource generates a Terraform list resource template. +func (o *GenerateTerraformProvider) GenerateTerraformListResource(resourceTyp properties.ResourceType, spec *properties.Normalization, provider *properties.TerraformProviderFile) error { + provider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/list", "") + provider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/list/schema", "listschema") + + names := NewNameProvider(spec, resourceTyp) + + funcMap := template.FuncMap{ + "structName": func() string { return names.StructName }, + "metaName": func() string { return names.MetaName }, + + "RenderModelStructs": func() (string, error) { + return RenderModelStructs(resourceTyp, properties.SchemaListResource, names, spec) + }, + "RenderMainStruct": func() (string, error) { + return RenderMainStruct(resourceTyp, properties.SchemaListResource, names, spec) + }, + "RenderConfigureFunc": func() (string, error) { + return RenderConfigureFunc(resourceTyp, properties.SchemaListResource, names, spec) + }, + "RenderSchema": func() (string, error) { + return RenderSchema(resourceTyp, properties.SchemaListResource, names, spec, provider.ImportManager) + }, + "RenderListFunc": func() (string, error) { + return RenderListFunc(resourceTyp, properties.SchemaListResource, names, spec, provider.ImportManager) + }, + } + + return o.generateTerraformEntityTemplate(resourceTyp, properties.SchemaListResource, names, spec, provider, "resource/list_resource.tmpl", funcMap) +} + // GenerateTerraformAction generates a Terraform action template. func (o *GenerateTerraformProvider) GenerateTerraformAction(spec *properties.Normalization, provider *properties.TerraformProviderFile) error { provider.ImportManager.AddStandardImport("context", "") @@ -288,7 +328,7 @@ func (o *GenerateTerraformProvider) GenerateTerraformAction(spec *properties.Nor "metaName": func() string { return names.MetaName }, "HasCustomValidation": func() bool { return spec.TerraformProviderConfig.CustomValidation }, - "RenderStructs": func() (string, error) { return RenderStructs(resourceTyp, properties.SchemaAction, names, spec) }, + "RenderModelStructs": func() (string, error) { return RenderModelStructs(resourceTyp, properties.SchemaAction, names, spec) }, "RenderSchema": func() (string, error) { return RenderSchema(resourceTyp, properties.SchemaAction, names, spec, provider.ImportManager) }, @@ -387,6 +427,15 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop "RenderDataSourceSchema": func() (string, error) { return RenderDataSourceSchema(resourceTyp, names, spec, terraformProvider.ImportManager) }, + "RenderMainStruct": func() (string, error) { + return RenderMainStruct(resourceTyp, properties.SchemaDataSource, names, spec) + }, + "RenderConfigureFunc": func() (string, error) { + return RenderConfigureFunc(resourceTyp, properties.SchemaDataSource, names, spec) + }, + "RenderModelStructs": func() (string, error) { + return RenderModelStructs(resourceTyp, properties.SchemaDataSource, names, spec) + }, } err := g.generateTerraformEntityTemplate(resourceTyp, properties.SchemaDataSource, names, spec, terraformProvider, "datasource/datasource.tmpl", funcMap) if err != nil { diff --git a/pkg/translate/terraform_provider/generator.go b/pkg/translate/terraform_provider/generator.go index 0a4bf816..be879df3 100644 --- a/pkg/translate/terraform_provider/generator.go +++ b/pkg/translate/terraform_provider/generator.go @@ -8,6 +8,7 @@ import ( "strings" "text/template" + "github.com/paloaltonetworks/pan-os-codegen/pkg/imports" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) @@ -80,9 +81,11 @@ func (g *GenerateTerraformProvider) appendResourceType(spec *properties.Normaliz case properties.SchemaEphemeralResource: flags |= properties.TerraformSpecEphemeralResource terraformProvider.EphemeralResources = append(terraformProvider.EphemeralResources, names.ResourceStructName) + case properties.SchemaListResource: + terraformProvider.ListResources = append(terraformProvider.ListResources, names.ListResourceStructName()) case properties.SchemaAction: terraformProvider.Actions = append(terraformProvider.Actions, names.ActionStructName()) - case properties.SchemaProvider, properties.SchemaCommon: + case properties.SchemaProvider, properties.SchemaCommon, properties.SchemaCustom: default: panic(fmt.Sprintf("unsupported schemaTyp: '%s'", schemaTyp)) } @@ -117,13 +120,15 @@ func (g *GenerateTerraformProvider) generateTerraformEntityTemplate(resourceTyp resourceType = "Resource" case properties.SchemaEphemeralResource: resourceType = "EphemeralResource" + case properties.SchemaListResource: + resourceType = "ListResource" case properties.SchemaCommon: resourceType = "Common" case properties.SchemaProvider: resourceType = "ProviderFile" case properties.SchemaAction: resourceType = "Action" - default: + case properties.SchemaCustom: panic(fmt.Sprintf("unsupported schemaTyp: '%+v'", schemaTyp)) } @@ -134,3 +139,87 @@ func (g *GenerateTerraformProvider) generateTerraformEntityTemplate(resourceTyp } return g.executeTemplate(template, spec, terraformProvider, resourceTyp, schemaTyp, names) } + +func renderMainAndConfigureTmpls(tmpl string, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization) (string, error) { + type context struct { + BareStructName string + StructName string + PackageName string + SDKName string + IsCustom bool + IsImportableEntry bool + IsEntry bool + IsUuid bool + IsConfig bool + } + + data := context{} + + switch resourceTyp { + case properties.ResourceConfig: + data.IsConfig = true + case properties.ResourceCustom: + data.IsCustom = true + case properties.ResourceEntry, properties.ResourceEntryPlural: + if len(spec.Imports.Variants) > 0 { + data.IsImportableEntry = true + } else { + data.IsEntry = true + } + case properties.ResourceUuid, properties.ResourceUuidPlural: + data.IsUuid = true + } + + data.BareStructName = names.StructName + data.SDKName = names.PackageName + + switch schemaTyp { + case properties.SchemaListResource: + data.StructName = names.ListResourceStructName() + case properties.SchemaDataSource: + data.StructName = names.DataSourceStructName + case properties.SchemaResource, properties.SchemaEphemeralResource: + data.StructName = names.ResourceStructName + case properties.SchemaAction, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: + } + + if spec.TerraformProviderConfig.Ephemeral { + data.PackageName = "ephemeral" + } else { + data.PackageName = "resource" + } + + return processTemplate(tmpl, "render-main-and-configure-tmpls", data, commonFuncMap) +} + +func RenderMainStruct(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization) (string, error) { + return renderMainAndConfigureTmpls("common/structure.tmpl", resourceTyp, schemaTyp, names, spec) +} + +func RenderConfigureFunc(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization) (string, error) { + return renderMainAndConfigureTmpls("common/configure_func.tmpl", resourceTyp, schemaTyp, names, spec) +} + +func RenderListFunc(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization, manager *imports.Manager) (string, error) { + type context struct { + StructName string + IdentityModel string + ResourceStructName string + SDKName string + } + + data := context{ + StructName: names.ListResourceStructName(), + IdentityModel: fmt.Sprintf("%sIdentityModel", names.ResourceStructName), + ResourceStructName: names.ResourceStructName, + SDKName: names.PackageName, + } + + funcMap := template.FuncMap{ + "RenderLocationsStateToPango": func(source string, dest string) (string, error) { + return RenderLocationsStateToPango(names, spec, source, dest, "diags") + }, + } + + return processTemplate("resource/list_func.tmpl", "render-list-func", data, funcMap) +} diff --git a/pkg/translate/terraform_provider/location.go b/pkg/translate/terraform_provider/location.go index 742ba365..89b068ab 100644 --- a/pkg/translate/terraform_provider/location.go +++ b/pkg/translate/terraform_provider/location.go @@ -358,11 +358,12 @@ func RenderLocationsPangoToState(names *NameProvider, spec *properties.Normaliza } // RenderLocationsStateToPango generates code to convert Terraform state to Pango locations. -func RenderLocationsStateToPango(names *NameProvider, spec *properties.Normalization, source string, dest string) (string, error) { +func RenderLocationsStateToPango(names *NameProvider, spec *properties.Normalization, source string, dest string, diags string) (string, error) { type context struct { TerraformStructName string Source string Dest string + Diags string Locations []locationCtx } data := context{ @@ -370,6 +371,7 @@ func RenderLocationsStateToPango(names *NameProvider, spec *properties.Normaliza Locations: renderLocationsGetContext(names, spec), Source: source, Dest: dest, + Diags: diags, } return processTemplate("location/state_to_pango.tmpl", "render-locations-state-to-pango", data, commonFuncMap) } diff --git a/pkg/translate/terraform_provider/provider_generators.go b/pkg/translate/terraform_provider/provider_generators.go index b076ea1d..6c2cde08 100644 --- a/pkg/translate/terraform_provider/provider_generators.go +++ b/pkg/translate/terraform_provider/provider_generators.go @@ -35,6 +35,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformProvider(terraformProvider terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/function", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/provider", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/action", "") + terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/list", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/provider/schema", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource", "") terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/ephemeral", "") @@ -45,6 +46,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformProvider(terraformProvider "RenderImports": func() (string, error) { return terraformProvider.ImportManager.RenderImports() }, "DataSources": func() []string { return terraformProvider.DataSources }, "EphemeralResources": func() []string { return terraformProvider.EphemeralResources }, + "ListResources": func() []string { return terraformProvider.ListResources }, "Resources": func() []string { return terraformProvider.Resources }, "Actions": func() []string { return terraformProvider.Actions }, "RenderResourceFuncMap": func() (string, error) { return RenderResourceFuncMap(terraformProvider.SpecMetadata) }, diff --git a/pkg/translate/terraform_provider/schema.go b/pkg/translate/terraform_provider/schema.go index b99bc483..c2110be1 100644 --- a/pkg/translate/terraform_provider/schema.go +++ b/pkg/translate/terraform_provider/schema.go @@ -232,7 +232,7 @@ func createSchemaSpecForParameter(schemaTyp properties.SchemaType, manager *impo case properties.SchemaResource, properties.SchemaEphemeralResource: computed = param.FinalComputed() required = param.FinalRequired() - case properties.SchemaCommon, properties.SchemaProvider: + case properties.SchemaListResource, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: panic("unreachable") } @@ -383,7 +383,7 @@ func createSchemaAttributeForParameter(schemaTyp properties.SchemaType, manager optional = param.FinalOptional() computed = param.FinalComputed() required = param.FinalRequired() - case properties.SchemaCommon, properties.SchemaProvider: + case properties.SchemaListResource, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: panic(fmt.Sprintf("unreachable for schemaTyp '%s'", schemaTyp)) } @@ -402,6 +402,34 @@ func createSchemaAttributeForParameter(schemaTyp properties.SchemaType, manager } } +func createSchemaSpecForListResourceModel(_ properties.ResourceType, _ properties.SchemaType, spec *properties.Normalization, packageName string, structName string, _ *imports.Manager) []schemaCtx { + var schemas []schemaCtx + var attributes []attributeCtx + + if len(spec.Locations) > 0 { + location := properties.NewNameVariant("location") + + attributes = append(attributes, attributeCtx{ + Package: packageName, + Name: location, + Required: true, + SchemaType: "SingleNestedAttribute", + }) + } + + schemas = append(schemas, schemaCtx{ + Package: packageName, + ObjectOrModel: "Model", + IsResource: false, + StructName: structName, + ReturnType: "Schema", + Attributes: attributes, + }) + + return schemas + +} + // createSchemaSpecForUuidModel creates a schema for uuid-type resources. func createSchemaSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, spec *properties.Normalization, packageName string, structName string, manager *imports.Manager) []schemaCtx { var schemas []schemaCtx @@ -607,11 +635,11 @@ func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp pro } case properties.SchemaEphemeralResource: packageName = "ephschema" + case properties.SchemaListResource: + packageName = "listschema" case properties.SchemaAction: packageName = "schema" - case properties.SchemaCommon, properties.SchemaProvider: - fallthrough - default: + case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: panic(fmt.Sprintf("unsupported schemaTyp: '%s'", schemaTyp)) } @@ -627,14 +655,18 @@ func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp pro structName = names.DataSourceStructName case properties.SchemaResource, properties.SchemaEphemeralResource: structName = names.ResourceStructName + case properties.SchemaListResource: + structName = names.ListResourceStructName() case properties.SchemaAction: structName = names.ActionStructName() - case properties.SchemaCommon, properties.SchemaProvider: - fallthrough - default: + case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: panic(fmt.Sprintf("unsupported schemaTyp: '%s'", schemaTyp)) } + if schemaTyp == properties.SchemaListResource { + return createSchemaSpecForListResourceModel(resourceTyp, schemaTyp, spec, packageName, structName, manager) + } + switch resourceTyp { case properties.ResourceEntry, properties.ResourceCustom, properties.ResourceConfig: return createSchemaSpecForEntrySingularModel(resourceTyp, schemaTyp, spec, packageName, structName, manager) diff --git a/pkg/translate/terraform_provider/struct.go b/pkg/translate/terraform_provider/struct.go index baa52da3..6f9d8974 100644 --- a/pkg/translate/terraform_provider/struct.go +++ b/pkg/translate/terraform_provider/struct.go @@ -1,5 +1,12 @@ package terraform_provider +import ( + "fmt" + "strings" + + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" +) + type Field struct { Name string Type string @@ -20,21 +27,135 @@ func ParamToModelBasic(paramName string, paramProp interface{}) (string, error) for k, v := range paramPropMap { data[k] = v } - return processTemplate("provider/model_field.tmpl", "param-to-model", data, nil) + + return processTemplate(` +{{- /* Begin */ -}} +{{ " " }}{{ CamelCaseName .paramName }} types.{{ CamelCaseType .Type }} `+"`"+`tfsdk:"{{ UnderscoreName .paramName }}"`+"`"+` +{{- /* Done */ -}}`, "param-to-model", data, nil) } // ParamToSchemaProvider converts the given parameter name and properties to a schema representation. func ParamToSchemaProvider(paramName string, paramProp interface{}) (string, error) { - data := map[string]interface{}{ - "paramName": paramName, - } - paramPropMap := structToMap(paramProp) - for k, v := range paramPropMap { - data[k] = v + return processTemplate(` +{{- /* Begin */ -}} + "`+paramName+`": schema.{{ CamelCaseType .Type }}Attribute{ + Description: ProviderParamDescription( + "{{ .Description }}", + "{{ .DefaultValue }}", + "{{ .EnvName }}", + "`+paramName+`", + ), + Optional: {{ .Optional }}, +{{- if .Sensitive }} + Sensitive: true, +{{- end }} +{{- if .Items }} + ElementType: types.{{CamelCaseType .Items.Type}}Type, +{{- end }} + }, +{{- /* Done */ -}}`, "describe-param", paramProp, nil) +} + +func ParamToSchemaResource(paramName string, paramProp interface{}, terraformProvider *properties.TerraformProviderFile) (string, error) { + switch v := paramProp.(type) { + case *properties.SpecParam: + if v.Type == "bool" && v.Default != "" { + terraformProvider.ImportManager.AddHashicorpImport("github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault", "") + } + + return processTemplate(` +{{- /* Begin */ -}} +{{- if .Type }} + "`+strings.ReplaceAll(paramName, "-", "_")+`": rsschema.{{ CamelCaseType .Type }}Attribute{ +{{- else }} + "`+strings.ReplaceAll(paramName, "-", "_")+`": rsschema.SingleNestedAttribute{ +{{- end }} + Description: "{{ .Description }}", + {{- if .Required }} + Required: true, + {{- else }} + Optional: true, + {{- end }} + {{- if .Items }} + ElementType: types.{{CamelCaseType .Items.Type}}Type, + {{- end }} + {{- if .Default }} + Default: {{.Type}}default.Static{{ CamelCaseType .Type }}({{- if eq .Type "string" }}{{ printf "%q" .Default }}{{ else if eq .Type "bool" }}{{ .Default }}{{ else }}{{ .Default }}{{ end }}), + Computed: true , + {{- end }} + }, +{{- /* Done */ -}}`, "describe-param", v, nil) + + case *properties.Location: + return processTemplate(` +{{- /* Begin */ -}} +{{- if .Vars }} + "`+strings.ReplaceAll(paramName, "-", "_")+`": rsschema.SingleNestedAttribute{ +{{- else }} + "`+strings.ReplaceAll(paramName, "-", "_")+`": rsschema.StringAttribute{ +{{- end }} + Description: "{{ .Description }}", + Required: true, + }, +{{- /* Done */ -}}`, "describe-location", v, nil) + + default: + return "", fmt.Errorf("unsupported type: %T", paramProp) } - return processTemplate("provider/schema_attribute.tmpl", "param-to-schema", data, nil) } func CreateResourceSchemaLocationAttribute() (string, error) { - return processTemplate("schema/location_attribute.tmpl", "resource-schema-location", nil, nil) + // Stub implementation - resource schema location attribute not yet implemented + return "", nil +} + +// CreateTfIdStruct generates a template for a struct based on the provided structType and structName. +func CreateTfIdStruct(structType string, structName string) (string, error) { + if structType == "entry" { + return processTemplate(` +{{- /* Begin */ -}} + Name string `+"`json:\"name\"`"+` + Location `+structName+`.Location `+"`json:\"location\"`"+` +{{- /* Done */ -}}`, "describe-param", nil, nil) + } else { + return processTemplate(` +{{- /* Begin */ -}} + Location `+structName+`.Location `+"`json:\"location\"`"+` +{{- /* Done */ -}}`, "describe-param", nil, nil) + } +} + +// CreateTfIdResourceModel generates a Terraform resource struct part for TFID. +func CreateTfIdResourceModel(structType string, structName string) (string, error) { + if structType == "entry" { + return processTemplate(` +{{- /* Begin */ -}} + Tfid types.String `+"`tfsdk:\"tfid\"`"+` + Location `+structName+`Location `+"`tfsdk:\"location\"`"+` +{{- /* Done */ -}}`, "describe-param", nil, nil) + } else { + return processTemplate(` +{{- /* Begin */ -}} + Tfid types.String `+"`tfsdk:\"tfid\"`"+` + Location `+structName+`Location `+"`tfsdk:\"location\"`"+` +{{- /* Done */ -}}`, "describe-param", nil, nil) + } +} + +// ParamToModelResource converts the given parameter name and properties to a model representation. +func ParamToModelResource(paramName string, paramProp *properties.SpecParam, structName string) (string, error) { + data := map[string]interface{}{ + "Name": paramName, + "Type": paramProp.Type, + "structName": structName, + } + templateText := ` +{{- /* Begin */ -}} + {{- if eq .Type "" }} + {{ CamelCaseName .Name }} *{{ .structName }}{{ CamelCaseName .Name }}Object ` + "`tfsdk:\"{{ UnderscoreName .Name }}\"`" + ` + {{- else }} + {{ CamelCaseName .Name }} types.{{ CamelCaseType .Type }} ` + "`tfsdk:\"{{ UnderscoreName .Name }}\"`" + ` + {{- end -}} +{{- /* Done */ -}}` + return processTemplate(templateText, "param-to-model", data, nil) } diff --git a/pkg/translate/terraform_provider/structs_model.go b/pkg/translate/terraform_provider/structs_model.go index 6f98fec4..f4b65bae 100644 --- a/pkg/translate/terraform_provider/structs_model.go +++ b/pkg/translate/terraform_provider/structs_model.go @@ -215,7 +215,7 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp structName = names.ResourceStructName case properties.SchemaDataSource: structName = names.DataSourceStructName - case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction: + case properties.SchemaListResource, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction, properties.SchemaCustom: panic("unreachable") } @@ -274,7 +274,7 @@ func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, sche structName = names.ResourceStructName case properties.SchemaDataSource: structName = names.DataSourceStructName - case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction: + case properties.SchemaListResource, properties.SchemaCommon, properties.SchemaProvider, properties.SchemaAction, properties.SchemaCustom: panic("unreachable") } @@ -363,12 +363,12 @@ func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTy structName = names.DataSourceStructName case properties.SchemaResource, properties.SchemaEphemeralResource: structName = names.ResourceStructName + case properties.SchemaListResource: + structName = names.ListResourceStructName() case properties.SchemaAction: structName = names.ActionStructName() - case properties.SchemaCommon, properties.SchemaProvider: + case properties.SchemaCommon, properties.SchemaProvider, properties.SchemaCustom: panic("unreachable") - default: - panic(fmt.Sprintf("unsupported schemaTyp: '%s'", schemaTyp)) } normalizationFields, normalizationStructs := createStructSpecForNormalization(resourceTyp, structName, spec, hackStructAsTypeObjects) @@ -391,6 +391,10 @@ func createStructSpecForModel(resourceTyp properties.ResourceType, schemaTyp pro return nil } + if schemaTyp == properties.SchemaListResource { + return createStructSpecForListResourceModel(resourceTyp, schemaTyp, spec, names) + } + switch resourceTyp { case properties.ResourceEntry, properties.ResourceCustom, properties.ResourceConfig: return createStructSpecForEntryModel(resourceTyp, schemaTyp, spec, names, hackStructsAsTypeObjects) @@ -645,6 +649,30 @@ func createValidatorSpecForNormalization(resourceTyp properties.ResourceType, st return fields, nestedSpecs, nestedObjects } +func createStructSpecForListResourceModel(_ properties.ResourceType, _ properties.SchemaType, spec *properties.Normalization, names *NameProvider) []datasourceStructSpec { + var structs []datasourceStructSpec + + var fields []datasourceStructFieldSpec + + if len(spec.Locations) > 0 { + fields = append(fields, datasourceStructFieldSpec{ + Name: properties.NewNameVariant("location"), + TerraformType: fmt.Sprintf("%sLocation", names.StructName), + TerraformStructType: fmt.Sprintf("%sLocation", names.StructName), + Type: "types.Object", + Tags: []string{"`tfsdk:\"location\"`"}, + }) + } + + structs = append(structs, datasourceStructSpec{ + StructName: names.ListResourceStructName(), + ModelOrObject: "Model", + Fields: fields, + }) + + return structs +} + // createValidatorSpecForEntryModel creates validator specs for entry-type singular resources. func createValidatorSpecForEntryModel(resourceTyp properties.ResourceType, names *NameProvider, spec *properties.Normalization, manager *imports.Manager) []modelValidatorSpec { if spec.Spec == nil { @@ -860,3 +888,16 @@ func RenderModelAttributeTypesFunction(resourceTyp properties.ResourceType, sche return processTemplate("common/attribute_types.tmpl", "attribute-types", data, nil) } + +// RenderModelStructs generates model struct definitions. +func RenderModelStructs(resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, names *NameProvider, spec *properties.Normalization) (string, error) { + type context struct { + Structs []datasourceStructSpec + } + + data := context{ + Structs: createStructSpecForModel(resourceTyp, schemaTyp, spec, names, true), + } + + return processTemplate("datasource/datasource_structs.tmpl", "render-model-structs", data, commonFuncMap) +} diff --git a/specs/schema/object.schema.json b/specs/schema/object.schema.json index c951d125..9897908f 100644 --- a/specs/schema/object.schema.json +++ b/specs/schema/object.schema.json @@ -22,7 +22,9 @@ "properties": { "custom_validation": { "type": "boolean" }, "skip_datasource": { "type": "boolean" }, + "skip_datasource_listing": { "type": "boolean" }, "skip_resource": { "type": "boolean" }, + "skip_list_resource": { "type": "boolean", "default": true }, "description": { "type": "string" }, "resource_type": { "type": "string", diff --git a/templates/terraform-provider/action/action.tmpl b/templates/terraform-provider/action/action.tmpl index 5e788454..18b8e5eb 100644 --- a/templates/terraform-provider/action/action.tmpl +++ b/templates/terraform-provider/action/action.tmpl @@ -11,7 +11,7 @@ type {{ structName }} struct { client *pango.Client } -{{ RenderStructs }} +{{ RenderModelStructs }} {{ RenderSchema }} diff --git a/templates/terraform-provider/common/configure_func.tmpl b/templates/terraform-provider/common/configure_func.tmpl new file mode 100644 index 00000000..e72e0df6 --- /dev/null +++ b/templates/terraform-provider/common/configure_func.tmpl @@ -0,0 +1,50 @@ +func (o *{{ .StructName }}) Configure(ctx context.Context, req {{ .PackageName }}.ConfigureRequest, resp *{{ .PackageName }}.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + providerData := req.ProviderData.(*ProviderData) + o.client = providerData.Client + +{{- if .IsCustom }} + custom, err := New{{ .BareStructName }}Custom(providerData) + if err != nil { + resp.Diagnostics.AddError("Failed to configure SDK client", err.Error()) + return + } + o.custom = custom +{{- else if and .IsImportableEntry }} + specifier, _, err := {{ .SDKName }}.Versioning(o.client.Versioning()) + if err != nil { + resp.Diagnostics.AddError("Failed to configure SDK client", err.Error()) + return + } + + batchSize := providerData.MultiConfigBatchSize + o.manager = sdkmanager.NewImportableEntryObjectManager(o.client, {{ .SDKName }}.NewService(o.client), batchSize, specifier, {{ .SDKName }}.SpecMatches) +{{- else if .IsEntry }} + specifier, _, err := {{ .SDKName }}.Versioning(o.client.Versioning()) + if err != nil { + resp.Diagnostics.AddError("Failed to configure SDK client", err.Error()) + return + } + batchSize := providerData.MultiConfigBatchSize + o.manager = sdkmanager.NewEntryObjectManager[*{{ .SDKName }}.Entry, {{ .SDKName }}.Location, *{{ .SDKName }}.Service](o.client, {{ .SDKName }}.NewService(o.client), batchSize, specifier, {{ .SDKName }}.SpecMatches) +{{- else if .IsUuid }} + specifier, _, err := {{ .SDKName }}.Versioning(o.client.Versioning()) + if err != nil { + resp.Diagnostics.AddError("Failed to configure SDK client", err.Error()) + return + } + batchSize := providerData.MultiConfigBatchSize + o.manager = sdkmanager.NewUuidObjectManager[*{{ .SDKName }}.Entry, {{ .SDKName }}.Location, *{{ .SDKName }}.Service](o.client, {{ .SDKName }}.NewService(o.client), batchSize, specifier, {{ .SDKName }}.SpecMatches) +{{- else if .IsConfig }} + specifier, _, err := {{ .SDKName }}.Versioning(o.client.Versioning()) + if err != nil { + resp.Diagnostics.AddError("Failed to configure SDK client", err.Error()) + return + } + o.manager = sdkmanager.NewConfigObjectManager(o.client, {{ .SDKName }}.NewService(o.client), specifier) +{{- end }} +} diff --git a/templates/terraform-provider/common/structure.tmpl b/templates/terraform-provider/common/structure.tmpl new file mode 100644 index 00000000..b5470e67 --- /dev/null +++ b/templates/terraform-provider/common/structure.tmpl @@ -0,0 +1,14 @@ +type {{ .StructName }} struct { + client *pango.Client +{{- if .IsCustom }} + custom *{{ .BareStructName }}Custom +{{- else if .IsImportableEntry }} + manager *sdkmanager.ImportableEntryObjectManager[*{{ .SDKName }}.Entry, {{ .SDKName }}.Location, *{{ .SDKName }}.Service] +{{- else if .IsEntry }} + manager *sdkmanager.EntryObjectManager[*{{ .SDKName }}.Entry, {{ .SDKName }}.Location, *{{ .SDKName }}.Service] +{{- else if .IsUuid }} + manager *sdkmanager.UuidObjectManager[*{{ .SDKName }}.Entry, {{ .SDKName }}.Location, *{{ .SDKName }}.Service] +{{- else if .IsConfig }} + manager *sdkmanager.ConfigObjectManager[*{{ .SDKName }}.Config, {{ .SDKName }}.Location, *{{ .SDKName }}.Service] +{{- end }} +} diff --git a/templates/terraform-provider/location/state_to_pango.tmpl b/templates/terraform-provider/location/state_to_pango.tmpl index d165b017..11d3a572 100644 --- a/templates/terraform-provider/location/state_to_pango.tmpl +++ b/templates/terraform-provider/location/state_to_pango.tmpl @@ -1,23 +1,26 @@ var terraformLocation {{ .TerraformStructName }} -{ -resp.Diagnostics.Append({{ $.Source }}.As(ctx, &terraformLocation, basetypes.ObjectAsOptions{})...) -if resp.Diagnostics.HasError() { - return -} +terraformLocationToPango := func() diag.Diagnostics { + diags := {{ $.Source }}.As(ctx, &terraformLocation, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return diags + } {{- range .Locations }} -{{ $locationType := .Name }} -if !terraformLocation.{{ $locationType }}.IsNull() { - {{ $.Dest }}.{{ $locationType }} = &{{ .PangoStructName }}{} - var innerLocation {{ .TerraformStructName }} - resp.Diagnostics.Append(terraformLocation.{{ .Name }}.As(ctx, &innerLocation, basetypes.ObjectAsOptions{})...) - if resp.Diagnostics.HasError() { - return - } + {{ $locationType := .Name }} + if !terraformLocation.{{ $locationType }}.IsNull() { + {{ $.Dest }}.{{ $locationType }} = &{{ .PangoStructName }}{} + var innerLocation {{ .TerraformStructName }} + diags.Append(terraformLocation.{{ .Name }}.As(ctx, &innerLocation, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return diags + } {{- range .Fields }} - {{ $.Dest }}.{{ $locationType }}.{{ .PangoName }} = innerLocation.{{ .TerraformName }}.ValueString() + {{ $.Dest }}.{{ $locationType }}.{{ .PangoName }} = innerLocation.{{ .TerraformName }}.ValueString() {{- end }} -} + } {{- end }} + return nil } + +{{ $.Diags }}.Append(terraformLocationToPango()...) \ No newline at end of file diff --git a/templates/terraform-provider/provider/provider.tmpl b/templates/terraform-provider/provider/provider.tmpl index f24280e7..ada976e8 100644 --- a/templates/terraform-provider/provider/provider.tmpl +++ b/templates/terraform-provider/provider/provider.tmpl @@ -8,6 +8,7 @@ var ( _ provider.Provider = &PanosProvider{} _ provider.ProviderWithFunctions = &PanosProvider{} _ provider.ProviderWithActions = &PanosProvider{} + _ provider.ProviderWithListResources = &PanosProvider{} ) // PanosProvider is the provider implementation. @@ -138,6 +139,7 @@ func (p *PanosProvider) Configure(ctx context.Context, req provider.ConfigureReq resp.ResourceData = providerData resp.EphemeralResourceData = providerData resp.ActionData = providerData + resp.ListResourceData = providerData // Done. tflog.Info(ctx, "Configured client", map[string]any{"success": true}) @@ -169,6 +171,14 @@ func (p *PanosProvider) EphemeralResources(_ context.Context) []func() ephemeral } } +func (p *PanosProvider) ListResources(_ context.Context) []func() list.ListResource { + return []func() list.ListResource{ +{{- range $fnName := ListResources }} + New{{ $fnName }}, +{{- end }} + } +} + func (p *PanosProvider) Actions(_ context.Context) []func() action.Action { return []func() action.Action{ {{- range $fnName := Actions }} diff --git a/templates/terraform-provider/resource/create_entry_list.tmpl b/templates/terraform-provider/resource/create_entry_list.tmpl index 5ec000ee..fcc1da97 100644 --- a/templates/terraform-provider/resource/create_entry_list.tmpl +++ b/templates/terraform-provider/resource/create_entry_list.tmpl @@ -18,6 +18,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} type entryWithState struct { Entry *{{ $resourceSDKStructName }} diff --git a/templates/terraform-provider/resource/create_many.tmpl b/templates/terraform-provider/resource/create_many.tmpl index 8a2d1025..3c1d23d2 100644 --- a/templates/terraform-provider/resource/create_many.tmpl +++ b/templates/terraform-provider/resource/create_many.tmpl @@ -18,6 +18,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} var elements []{{ $resourceTFStructName }} resp.Diagnostics.Append(state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)...) diff --git a/templates/terraform-provider/resource/delete_many.tmpl b/templates/terraform-provider/resource/delete_many.tmpl index 0f18a3dd..844067b3 100644 --- a/templates/terraform-provider/resource/delete_many.tmpl +++ b/templates/terraform-provider/resource/delete_many.tmpl @@ -30,6 +30,9 @@ if resp.Diagnostics.HasError() { var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} var names []string {{- if eq .PluralType "map" }} diff --git a/templates/terraform-provider/resource/list_func.tmpl b/templates/terraform-provider/resource/list_func.tmpl new file mode 100644 index 00000000..6db18eb0 --- /dev/null +++ b/templates/terraform-provider/resource/list_func.tmpl @@ -0,0 +1,68 @@ +func (o *{{ .StructName }}) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data {{ .StructName }}Model + + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + var location {{ .SDKName }}.Location + {{ RenderLocationsStateToPango "data.Location" "location" }} + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + components := []string{} + entries, err := o.manager.ReadMany(ctx, location, components) + if err != nil && !errors.Is(err, sdkmanager.ErrObjectNotFound) { + stream.Results = list.ListResultsStreamDiagnostics(diag.Diagnostics{ + diag.NewErrorDiagnostic("Error while listing entries on the server", err.Error()), + }) + return + } + + stream.Results = func(push func(list.ListResult) bool) { + for _, elt := range entries { + result := req.NewListResult(ctx) + + identityLocation, diags := terraformLocation.Identity(ctx) + result.Diagnostics.Append(diags...) + if result.Diagnostics.HasError() { + push(result) + return + } + + identity := {{ .IdentityModel }}{ + Name: types.StringValue(elt.EntryName()), + Location: identityLocation, + } + + result.Diagnostics.Append(result.Identity.Set(ctx, identity)...) + if result.Diagnostics.HasError() { + push(result) + return + } + + var object {{ .ResourceStructName }}Model + object.Location = data.Location + + result.Diagnostics.Append(object.CopyFromPango(ctx, o.client, nil, elt, nil)...) + if result.Diagnostics.HasError() { + push(result) + return + } + + result.Diagnostics.Append(result.Resource.Set(ctx, object)...) + if result.Diagnostics.HasError() { + push(result) + return + } + + if !push(result) { + return + } + } + } +} diff --git a/templates/terraform-provider/resource/list_resource.tmpl b/templates/terraform-provider/resource/list_resource.tmpl new file mode 100644 index 00000000..f0f46f4f --- /dev/null +++ b/templates/terraform-provider/resource/list_resource.tmpl @@ -0,0 +1,29 @@ +var ( + _ list.ListResourceWithConfigure = &{{ structName }}ListResource{} +) + +func New{{ structName }}ListResource() list.ListResource { + return &{{ structName }}ListResource{} +} + +func (o *{{ structName }}ListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "{{ metaName }}" +} + +{{ RenderMainStruct }} + +{{ RenderConfigureFunc }} + +{{ RenderModelStructs }} + +{{ RenderSchema }} + +func {{ structName }}ListResourceLocationSchema() listschema.Attribute { + return {{ structName }}LocationSchema() +} + +func (o *{{ structName }}ListResource) ListResourceConfigSchema(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = {{ structName }}ListResourceSchema() +} + +{{ RenderListFunc }} diff --git a/templates/terraform-provider/resource/read.tmpl b/templates/terraform-provider/resource/read.tmpl index 4011730d..244633d8 100644 --- a/templates/terraform-provider/resource/read.tmpl +++ b/templates/terraform-provider/resource/read.tmpl @@ -14,6 +14,9 @@ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} + if resp.Diagnostics.HasError() { + return; + } // Basic logging. tflog.Info(ctx, "performing resource read", map[string]any{ diff --git a/templates/terraform-provider/resource/read_entry_list.tmpl b/templates/terraform-provider/resource/read_entry_list.tmpl index 9ac9f9ca..a26f46f0 100644 --- a/templates/terraform-provider/resource/read_entry_list.tmpl +++ b/templates/terraform-provider/resource/read_entry_list.tmpl @@ -34,6 +34,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} {{- if eq .PluralType "map" }} elements := make(map[string]{{ $resourceTFStructName }}) diff --git a/templates/terraform-provider/resource/read_many.tmpl b/templates/terraform-provider/resource/read_many.tmpl index 0a5d5cfc..ff4e7a3e 100644 --- a/templates/terraform-provider/resource/read_many.tmpl +++ b/templates/terraform-provider/resource/read_many.tmpl @@ -32,6 +32,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} var elements []{{ $resourceTFStructName }} resp.Diagnostics.Append(state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)...) diff --git a/templates/terraform-provider/resource/update.tmpl b/templates/terraform-provider/resource/update.tmpl index 55b4d77a..817a9d12 100644 --- a/templates/terraform-provider/resource/update.tmpl +++ b/templates/terraform-provider/resource/update.tmpl @@ -12,6 +12,9 @@ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "state.Location" "location" }} + if resp.Diagnostics.HasError() { + return; + } // Basic logging. tflog.Info(ctx, "performing resource update", map[string]any{ diff --git a/templates/terraform-provider/resource/update_entry_list.tmpl b/templates/terraform-provider/resource/update_entry_list.tmpl index ca678288..6386e306 100644 --- a/templates/terraform-provider/resource/update_entry_list.tmpl +++ b/templates/terraform-provider/resource/update_entry_list.tmpl @@ -19,6 +19,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "plan.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} // Basic logging. tflog.Info(ctx, "performing resource update", map[string]any{ diff --git a/templates/terraform-provider/resource/update_many.tmpl b/templates/terraform-provider/resource/update_many.tmpl index ac760926..35b785eb 100644 --- a/templates/terraform-provider/resource/update_many.tmpl +++ b/templates/terraform-provider/resource/update_many.tmpl @@ -19,6 +19,9 @@ tflog.Info(ctx, "performing resource create", map[string]any{ var location {{ .resourceSDKName }}.Location {{ RenderLocationsStateToPango "plan.Location" "location" }} +if resp.Diagnostics.HasError() { + return; +} var elements []{{ $resourceTFStructName }} resp.Diagnostics.Append(state.{{ .ListAttribute.CamelCase }}.ElementsAs(ctx, &elements, false)...)