From cac6cf7aa9a2de36d8cdd457f7ccbb98175cb3a2 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Fri, 6 Jun 2025 21:24:16 +0200 Subject: [PATCH 1/7] feat(codegen): use terraform types for structures and lists --- pkg/translate/terraform_provider/funcs.go | 231 ++++++++++++------ .../terraform_provider_file.go | 8 +- 2 files changed, 162 insertions(+), 77 deletions(-) diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index e343df53..5499f77a 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -36,6 +36,7 @@ type parameterEncryptionSpec struct { type parameterSpec struct { PangoName *properties.NameVariant TerraformName *properties.NameVariant + TerraformType string ComplexType string Type string Required bool @@ -50,12 +51,13 @@ type spec struct { PangoType string PangoReturnType string TerraformType string + TerraformStructType string ModelOrObject string Params []parameterSpec OneOf []parameterSpec } -func renderSpecsForParams(ancestors []*properties.SpecParam, params []*properties.SpecParam) []parameterSpec { +func renderSpecsForParams(names *NameProvider, schemaTyp properties.SchemaType, ancestors []*properties.SpecParam, params []*properties.SpecParam) []parameterSpec { var specs []parameterSpec for _, elt := range params { if elt.IsTerraformOnly() { @@ -89,9 +91,24 @@ func renderSpecsForParams(ancestors []*properties.SpecParam, params []*propertie itemsType = elt.Items.Type } + var structPrefix string + switch schemaTyp { + case properties.SchemaDataSource: + structPrefix = names.DataSourceStructName + case properties.SchemaResource, properties.SchemaEphemeralResource: + structPrefix = names.ResourceStructName + case properties.SchemaCommon, properties.SchemaProvider: + panic(fmt.Sprintf("invalid schema type: %s", schemaTyp)) + } + + for _, elt := range ancestors { + structPrefix += elt.TerraformNameVariant().CamelCase + } + specs = append(specs, parameterSpec{ PangoName: elt.PangoNameVariant(), TerraformName: elt.TerraformNameVariant(), + TerraformType: terraformTypeForProperty(structPrefix, elt, false), ComplexType: elt.ComplexType(), Type: elt.FinalType(), ItemsType: itemsType, @@ -102,7 +119,7 @@ func renderSpecsForParams(ancestors []*properties.SpecParam, params []*propertie return specs } -func generateFromTerraformToPangoSpec(pangoTypePrefix string, terraformPrefix string, paramSpec *properties.SpecParam, ancestors []*properties.SpecParam) []spec { +func generateFromTerraformToPangoSpec(names *NameProvider, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, pangoTypePrefix string, terraformPrefix string, paramSpec *properties.SpecParam, ancestors []*properties.SpecParam) []spec { if paramSpec.Spec == nil { return nil } @@ -116,8 +133,8 @@ func generateFromTerraformToPangoSpec(pangoTypePrefix string, terraformPrefix st ancestors = append(ancestors, paramSpec) - paramSpecs := renderSpecsForParams(ancestors, paramSpec.Spec.SortedParams()) - oneofSpecs := renderSpecsForParams(ancestors, paramSpec.Spec.SortedOneOf()) + paramSpecs := renderSpecsForParams(names, schemaTyp, ancestors, paramSpec.Spec.SortedParams()) + oneofSpecs := renderSpecsForParams(names, schemaTyp, ancestors, paramSpec.Spec.SortedOneOf()) var hasEntryName bool if paramSpec.Type == "list" && paramSpec.Items.Type == "entry" { @@ -143,7 +160,7 @@ func generateFromTerraformToPangoSpec(pangoTypePrefix string, terraformPrefix st } terraformPrefix := fmt.Sprintf("%s%s", terraformPrefix, paramSpec.TerraformNameVariant().CamelCase) - specs = append(specs, generateFromTerraformToPangoSpec(pangoType, terraformPrefix, elt, ancestors)...) + specs = append(specs, generateFromTerraformToPangoSpec(names, resourceTyp, schemaTyp, pangoType, terraformPrefix, elt, ancestors)...) } } @@ -153,7 +170,7 @@ func generateFromTerraformToPangoSpec(pangoTypePrefix string, terraformPrefix st return specs } -func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, pkgName string, terraformPrefix string, pangoPrefix string, prop *properties.Normalization, ancestors []*properties.SpecParam) []spec { +func generateFromTerraformToPangoParameter(names *NameProvider, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, pkgName string, terraformPrefix string, pangoPrefix string, prop *properties.Normalization, ancestors []*properties.SpecParam) []spec { var specs []spec var pangoReturnType string @@ -164,11 +181,18 @@ func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, pangoReturnType = fmt.Sprintf("%s.%s", pkgName, ancestors[0].Name.CamelCase) } - paramSpecs := renderSpecsForParams(ancestors, prop.Spec.SortedParams()) - oneofSpecs := renderSpecsForParams(ancestors, prop.Spec.SortedOneOf()) + switch resourceTyp { + case properties.ResourceEntry, properties.ResourceConfig: + case properties.ResourceEntryPlural, properties.ResourceUuid, properties.ResourceUuidPlural: + terraformPrefix = fmt.Sprintf("%s%s", terraformPrefix, pascalCase(prop.TerraformProviderConfig.PluralName)) + case properties.ResourceCustom: + panic("custom resources don't generate anything") + } switch resourceTyp { case properties.ResourceEntry, properties.ResourceConfig: + paramSpecs := renderSpecsForParams(names, schemaTyp, ancestors, prop.Spec.SortedParams()) + oneofSpecs := renderSpecsForParams(names, schemaTyp, ancestors, prop.Spec.SortedOneOf()) specs = append(specs, spec{ HasEntryName: prop.Entry != nil, PangoType: pangoPrefix, @@ -179,7 +203,14 @@ func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, OneOf: oneofSpecs, }) case properties.ResourceEntryPlural, properties.ResourceUuid, properties.ResourceUuidPlural: - terraformPrefix = fmt.Sprintf("%s%s", terraformPrefix, pascalCase(prop.TerraformProviderConfig.PluralName)) + ancestors = append(ancestors, &properties.SpecParam{ + Name: properties.NewNameVariant(prop.TerraformProviderConfig.PluralName), + Type: "list", + }) + + paramSpecs := renderSpecsForParams(names, schemaTyp, ancestors, prop.Spec.SortedParams()) + oneofSpecs := renderSpecsForParams(names, schemaTyp, ancestors, prop.Spec.SortedOneOf()) + var hasEntryName bool if prop.Entry != nil && (resourceTyp != properties.ResourceEntryPlural || prop.TerraformProviderConfig.PluralType != object.TerraformPluralMapType) { hasEntryName = true @@ -202,7 +233,7 @@ func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, continue } - specs = append(specs, generateFromTerraformToPangoSpec(pangoPrefix, terraformPrefix, elt, nil)...) + specs = append(specs, generateFromTerraformToPangoSpec(names, resourceTyp, schemaTyp, pangoPrefix, terraformPrefix, elt, ancestors)...) } for _, elt := range prop.Spec.SortedOneOf() { @@ -210,7 +241,7 @@ func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, continue } - specs = append(specs, generateFromTerraformToPangoSpec(pangoPrefix, terraformPrefix, elt, nil)...) + specs = append(specs, generateFromTerraformToPangoSpec(names, resourceTyp, schemaTyp, pangoPrefix, terraformPrefix, elt, ancestors)...) } return specs @@ -223,17 +254,21 @@ const copyToPangoTmpl = ` {{- $result := .TerraformName.LowerCamelCase }} {{- $diag := .TerraformName.LowerCamelCase | printf "%s_diags" }} var {{ $result }}_entry *{{ $.Spec.PangoType }}{{ .PangoName.CamelCase }} - if o.{{ .TerraformName.CamelCase }} != nil { + if !o.{{ .TerraformName.CamelCase }}.IsUnknown() && !o.{{ .TerraformName.CamelCase }}.IsNull() { if *obj != nil && (*obj).{{ .PangoName.CamelCase }} != nil { {{ $result }}_entry = (*obj).{{ .PangoName.CamelCase }} } else { {{ $result }}_entry = new({{ $.Spec.PangoType }}{{ .PangoName.CamelCase }}) } - // ModelOrObject: {{ $.Spec.ModelOrObject }} + var object {{ .TerraformType }} + diags.Append(o.{{ .TerraformName.CamelCase }}.As(ctx, &object, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return diags + } {{- if eq $.Spec.ModelOrObject "Model" }} - diags.Append(o.{{ .TerraformName.CamelCase }}.CopyToPango(ctx, ancestors, &{{ $result }}_entry, ev)...) + diags.Append(object.CopyToPango(ctx, ancestors, &{{ $result }}_entry, ev)...) {{- else }} - diags.Append(o.{{ .TerraformName.CamelCase }}.CopyToPango(ctx, append(ancestors, o), &{{ $result }}_entry, ev)...) + diags.Append(object.CopyToPango(ctx, append(ancestors, o), &{{ $result }}_entry, ev)...) {{- end }} if diags.HasError() { return diags @@ -268,10 +303,18 @@ const copyToPangoTmpl = ` } } {{- else }} - {{ $pangoEntries }} := make([]{{ .ItemsType }}, 0) - diags.Append(o.{{ .TerraformName.CamelCase }}.ElementsAs(ctx, &{{ $pangoEntries }}, false)...) - if diags.HasError() { - return diags + var {{ $pangoEntries }} []{{ .ItemsType }} + if !o.{{ .TerraformName.CamelCase }}.IsUnknown() && !o.{{ .TerraformName.CamelCase }}.IsNull() { + object_entries := make([]types.{{ .ItemsType | PascalCase }}, 0, len(o.{{ .TerraformName.CamelCase }}.Elements())) + diags.Append(o.{{ .TerraformName.CamelCase }}.ElementsAs(ctx, &object_entries, false)...) + if diags.HasError() { + diags.AddError("Explicit Error", "Failed something") + return diags + } + + for _, elt := range object_entries { + {{ $pangoEntries }} = append({{ $pangoEntries }}, elt.ValueString()) + } } {{- end }} {{- end }} @@ -410,7 +453,7 @@ const copyFromPangoTmpl = ` {{- end }} {{- define "renderListValueSimple" }} -var {{ .TerraformName.LowerCamelCase }}_list types.List +var {{ .TerraformName.LowerCamelCase }}_list types.{{ .Type | PascalCase }} { schema := rsschema.{{ .Type | PascalCase }}Attribute{} {{ .TerraformName.LowerCamelCase }}_list, {{ .TerraformName.LowerCamelCase }}_diags := types.ListValueFrom(ctx, obj.{{ .PangoName.CamelCase }}, schema.GetType()) @@ -534,18 +577,25 @@ var {{ .TerraformName.LowerCamelCase }}_list types.Set {{- with .Parameter }} {{- $result := .TerraformName.LowerCamelCase }} {{- $diag := .TerraformName.LowerCamelCase | printf "%s_diags" }} - var {{ $result }}_object *{{ $.Spec.TerraformType }}{{ .TerraformName.CamelCase }}Object + + {{ $result }}_obj := new({{ $.Spec.TerraformType }}{{ .TerraformName.CamelCase }}Object) + {{ $result }}_object := types.ObjectNull({{ $result }}_obj.AttributeTypes()) if obj.{{ .PangoName.CamelCase }} != nil { - {{ $result }}_object = new({{ $.Spec.TerraformType }}{{ .TerraformName.CamelCase }}Object) {{- if eq $.Spec.ModelOrObject "Model" }} - diags.Append({{ $result }}_object.CopyFromPango(ctx, ancestors, obj.{{ .PangoName.CamelCase }}, ev)...) + diags.Append({{ $result }}_obj.CopyFromPango(ctx, ancestors, obj.{{ .PangoName.CamelCase }}, ev)...) {{- else }} - diags.Append({{ $result }}_object.CopyFromPango(ctx, append(ancestors, o), obj.{{ .PangoName.CamelCase }}, ev)...) + diags.Append({{ $result }}_obj.CopyFromPango(ctx, append(ancestors, o), obj.{{ .PangoName.CamelCase }}, ev)...) {{- end }} if diags.HasError() { return diags } + var diags_tmp diag.Diagnostics + {{ $result }}_object, diags_tmp = types.ObjectValueFrom(ctx, {{ $result }}_obj.AttributeTypes(), {{ $result }}_obj) + diags.Append(diags_tmp...) + if diags.HasError() { + return diags + } } {{- end }} {{- end }} @@ -760,12 +810,12 @@ func RenderEncryptedValuesFinalizer(schemaTyp properties.SchemaType, spec *prope return processTemplate(encryptedValuesManagerFinalizerTmpl, "encrypted-values-manager-finalizer", data, nil) } -func RenderCopyToPangoFunctions(resourceTyp properties.ResourceType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { +func RenderCopyToPangoFunctions(names *NameProvider, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { if resourceTyp == properties.ResourceCustom { return "", nil } - specs := generateFromTerraformToPangoParameter(resourceTyp, pkgName, terraformTypePrefix, "", property, nil) + specs := generateFromTerraformToPangoParameter(names, resourceTyp, schemaTyp, pkgName, terraformTypePrefix, "", property, nil) type context struct { Specs []spec @@ -780,12 +830,12 @@ func RenderCopyToPangoFunctions(resourceTyp properties.ResourceType, pkgName str return processTemplate(copyToPangoTmpl, "copy-to-pango", data, funcMap) } -func RenderCopyFromPangoFunctions(resourceTyp properties.ResourceType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { +func RenderCopyFromPangoFunctions(names *NameProvider, resourceTyp properties.ResourceType, schemaTyp properties.SchemaType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { if resourceTyp == properties.ResourceCustom { return "", nil } - specs := generateFromTerraformToPangoParameter(resourceTyp, pkgName, terraformTypePrefix, "", property, nil) + specs := generateFromTerraformToPangoParameter(names, resourceTyp, schemaTyp, pkgName, terraformTypePrefix, "", property, nil) type context struct { Specs []spec @@ -2436,11 +2486,13 @@ type {{ .StructName }}{{ .ModelOrObject }} struct { ` type datasourceStructFieldSpec struct { - Name *properties.NameVariant - Private bool - TerraformType string - Type string - Tags []string + Name *properties.NameVariant + Private bool + TerraformType string + TerraformStructType string + Type string + ItemsType string + Tags []string } type datasourceStructSpec struct { @@ -2453,9 +2505,17 @@ type datasourceStructSpec struct { } func terraformTypeForProperty(structPrefix string, prop *properties.SpecParam, hackStructsAsTypeObjects bool) string { - if prop.Type == "" { + if prop.Type == "" || prop.Type == "list" && prop.Items.Type == "entry" { if hackStructsAsTypeObjects { - return "types.Object" + if prop.Type == "" { + return "types.Object" + } else if prop.FinalType() == "set" { + return "types.Set" + } else if prop.FinalType() == "map" { + return "types.Map" + } else { + return "types.List" + } } else { return fmt.Sprintf("*%s%sObject", structPrefix, prop.TerraformNameVariant().CamelCase) } @@ -2466,14 +2526,6 @@ func terraformTypeForProperty(structPrefix string, prop *properties.SpecParam, h return "types.String" } - if prop.FinalType() == "list" && prop.Items.Type == "entry" { - return "types.List" - } - - if prop.FinalType() == "set" && prop.Items.Type == "entry" { - return "types.Set" - } - if prop.FinalType() == "list" { return "types.List" } @@ -2488,11 +2540,22 @@ func terraformTypeForProperty(structPrefix string, prop *properties.SpecParam, h func structFieldSpec(param *properties.SpecParam, structPrefix string, hackStructsAsTypeObjects bool) datasourceStructFieldSpec { tfTag := fmt.Sprintf("`tfsdk:\"%s\"`", param.TerraformNameVariant().Underscore) + var itemsType string + if param.Type == "list" { + if param.Items.Type == "entry" { + itemsType = "types.Object" + } else { + itemsType = fmt.Sprintf("types.%sType", pascalCase(param.Items.Type)) + } + } + return datasourceStructFieldSpec{ - Name: param.TerraformNameVariant(), - TerraformType: terraformTypeForProperty(structPrefix, param, false), - Type: terraformTypeForProperty(structPrefix, param, hackStructsAsTypeObjects), - Tags: []string{tfTag}, + Name: param.TerraformNameVariant(), + TerraformType: terraformTypeForProperty(structPrefix, param, hackStructsAsTypeObjects), + TerraformStructType: terraformTypeForProperty(structPrefix, param, false), + Type: terraformTypeForProperty(structPrefix, param, hackStructsAsTypeObjects), + ItemsType: itemsType, + Tags: []string{tfTag}, } } @@ -2568,10 +2631,11 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp if len(spec.Locations) > 0 { fields = append(fields, datasourceStructFieldSpec{ - Name: properties.NewNameVariant("location"), - TerraformType: fmt.Sprintf("%sLocation", names.StructName), - Type: "types.Object", - Tags: []string{"`tfsdk:\"location\"`"}, + Name: properties.NewNameVariant("location"), + TerraformType: fmt.Sprintf("%sLocation", names.StructName), + TerraformStructType: fmt.Sprintf("%sLocation", names.StructName), + Type: "types.Object", + Tags: []string{"`tfsdk:\"location\"`"}, }) } @@ -2580,10 +2644,11 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp position := properties.NewNameVariant("position") fields = append(fields, datasourceStructFieldSpec{ - Name: position, - TerraformType: "TerraformPositionObject", - Type: "types.Object", - Tags: []string{"`tfsdk:\"position\"`"}, + Name: position, + TerraformType: "TerraformPositionObject", + TerraformStructType: "TerraformPositionObject", + Type: "types.Object", + Tags: []string{"`tfsdk:\"position\"`"}, }) } @@ -2602,9 +2667,11 @@ func createStructSpecForUuidModel(resourceTyp properties.ResourceType, schemaTyp tag := fmt.Sprintf("`tfsdk:\"%s\"`", listName.Underscore) fields = append(fields, datasourceStructFieldSpec{ - Name: listName, - Type: "types.List", - Tags: []string{tag}, + Name: listName, + Type: "types.List", + TerraformStructType: fmt.Sprintf("%s%sObject", structName, listName.CamelCase), + ItemsType: "types.Object", + Tags: []string{tag}, }) structs = append(structs, datasourceStructSpec{ @@ -2635,10 +2702,11 @@ func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, sche var fields []datasourceStructFieldSpec if len(spec.Locations) > 0 { fields = append(fields, datasourceStructFieldSpec{ - Name: properties.NewNameVariant("location"), - TerraformType: fmt.Sprintf("%sLocation", names.StructName), - Type: "types.Object", - Tags: []string{"`tfsdk:\"location\"`"}, + Name: properties.NewNameVariant("location"), + TerraformType: fmt.Sprintf("%sLocation", names.StructName), + TerraformStructType: fmt.Sprintf("%sLocation", names.StructName), + Type: "types.Object", + Tags: []string{"`tfsdk:\"location\"`"}, }) } @@ -2685,9 +2753,11 @@ func createStructSpecForEntryListModel(resourceTyp properties.ResourceType, sche tag := fmt.Sprintf("`tfsdk:\"%s\"`", listName.Underscore) fields = append(fields, datasourceStructFieldSpec{ - Name: listName, - Type: listEltType, - Tags: []string{tag}, + Name: listName, + Type: listEltType, + TerraformStructType: fmt.Sprintf("%s%sObject", structName, listName.CamelCase), + ItemsType: "types.Object", + Tags: []string{tag}, }) structs = append(structs, datasourceStructSpec{ @@ -2720,10 +2790,11 @@ func createStructSpecForEntryModel(resourceTyp properties.ResourceType, schemaTy if len(spec.Locations) > 0 { fields = append(fields, datasourceStructFieldSpec{ - Name: properties.NewNameVariant("location"), - TerraformType: fmt.Sprintf("%sLocation", names.StructName), - Type: "types.Object", - Tags: []string{"`tfsdk:\"location\"`"}, + Name: properties.NewNameVariant("location"), + TerraformType: fmt.Sprintf("%sLocation", names.StructName), + TerraformStructType: fmt.Sprintf("%sLocation", names.StructName), + Type: "types.Object", + Tags: []string{"`tfsdk:\"location\"`"}, }) } @@ -2832,7 +2903,7 @@ func RenderResourceStructs(resourceTyp properties.ResourceType, names *NameProvi } data := context{ - Structs: createStructSpecForModel(resourceTyp, properties.SchemaResource, spec, names, false), + Structs: createStructSpecForModel(resourceTyp, properties.SchemaResource, spec, names, true), } return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap) @@ -2844,7 +2915,7 @@ func RenderDataSourceStructs(resourceTyp properties.ResourceType, names *NamePro } data := context{ - Structs: createStructSpecForModel(resourceTyp, properties.SchemaDataSource, spec, names, false), + Structs: createStructSpecForModel(resourceTyp, properties.SchemaDataSource, spec, names, true), } return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap) @@ -2856,7 +2927,11 @@ func (o *{{ .StructName }}{{ .ModelOrObject }}) AttributeTypes() map[string]attr {{- range .Fields }} {{- if .Private }}{{ continue }}{{- end }} {{ if (eq .Type "types.Object") }} - var {{ .Name.LowerCamelCase }}Obj {{ .TerraformType }} + var {{ .Name.LowerCamelCase }}Obj {{ .TerraformStructType }} + {{- else if or (eq .Type "types.List") (eq .Type "types.Set") (eq .Type "types.Map") }} + {{- if eq .ItemsType "types.Object" }} + var {{ .Name.LowerCamelCase }}Obj {{ .TerraformStructType }} + {{- end }} {{- end }} {{- end }} return map[string]attr.Type{ @@ -2867,7 +2942,17 @@ func (o *{{ .StructName }}{{ .ModelOrObject }}) AttributeTypes() map[string]attr AttrTypes: {{ .Name.LowerCamelCase }}Obj.AttributeTypes(), }, {{- else if or (eq .Type "types.List") (eq .Type "types.Set") (eq .Type "types.Map") }} - "{{ .Name.Underscore }}": {{ .Type }}Type{}, + {{- if (eq .ItemsType "types.Object") }} + "{{ .Name.Underscore }}": {{ .Type }}Type{ + ElemType: types.ObjectType{ + AttrTypes: {{ .Name.LowerCamelCase }}Obj.AttributeTypes(), + }, + }, + {{- else }} + "{{ .Name.Underscore }}": {{ .Type }}Type{ + ElemType: {{ .ItemsType }}, + }, + {{- end }} {{- else }} "{{ .Name.Underscore }}": {{ .Type }}Type, {{- end }} diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index e01876c5..c531ac23 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -195,10 +195,10 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper return RenderModelAttributeTypesFunction(resourceTyp, properties.SchemaResource, names, spec) }, "RenderCopyToPangoFunctions": func() (string, error) { - return RenderCopyToPangoFunctions(resourceTyp, names.PackageName, names.ResourceStructName, spec) + return RenderCopyToPangoFunctions(names, resourceTyp, properties.SchemaResource, names.PackageName, names.ResourceStructName, spec) }, "RenderCopyFromPangoFunctions": func() (string, error) { - return RenderCopyFromPangoFunctions(resourceTyp, names.PackageName, names.ResourceStructName, spec) + return RenderCopyFromPangoFunctions(names, resourceTyp, properties.SchemaResource, names.PackageName, names.ResourceStructName, spec) }, "RenderXpathComponentsGetter": func() (string, error) { return RenderXpathComponentsGetter(names.ResourceStructName, spec) @@ -458,10 +458,10 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop return RenderModelAttributeTypesFunction(resourceTyp, properties.SchemaDataSource, names, spec) }, "RenderCopyToPangoFunctions": func() (string, error) { - return RenderCopyToPangoFunctions(resourceTyp, names.PackageName, names.DataSourceStructName, spec) + return RenderCopyToPangoFunctions(names, resourceTyp, properties.SchemaDataSource, names.PackageName, names.DataSourceStructName, spec) }, "RenderCopyFromPangoFunctions": func() (string, error) { - return RenderCopyFromPangoFunctions(resourceTyp, names.PackageName, names.DataSourceStructName, spec) + return RenderCopyFromPangoFunctions(names, resourceTyp, properties.SchemaDataSource, names.PackageName, names.DataSourceStructName, spec) }, "RenderXpathComponentsGetter": func() (string, error) { return RenderXpathComponentsGetter(names.DataSourceStructName, spec) From 747f651f2b4e5da57affd514c1ab9c1c5e514331 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Tue, 8 Jul 2025 09:57:57 +0200 Subject: [PATCH 2/7] feat(codegen): Add support for disabling of variant group validation Some of the variant groups have variants of different types (i.e. lists and strings) and the existing method of validation is not working correctly. This commit introduces an variant_check override value of "Disabled" that can be used to disable validation of the given variant group completely. --- pkg/schema/parameter/parameter.go | 1 + pkg/translate/imports.go | 3 -- pkg/translate/terraform_provider/funcs.go | 40 +++++++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/pkg/schema/parameter/parameter.go b/pkg/schema/parameter/parameter.go index 65b9594e..97823ace 100644 --- a/pkg/schema/parameter/parameter.go +++ b/pkg/schema/parameter/parameter.go @@ -47,6 +47,7 @@ type EnumSpecValue struct { type VariantCheckType string const ( + VariantCheckDisabled VariantCheckType = "Disabled" VariantCheckConflictsWith VariantCheckType = "ConflictsWith" VariantCheckExactlyOneOf VariantCheckType = "ExactlyOneOf" ) diff --git a/pkg/translate/imports.go b/pkg/translate/imports.go index 83c65636..fcd3529d 100644 --- a/pkg/translate/imports.go +++ b/pkg/translate/imports.go @@ -27,9 +27,6 @@ func RenderImports(spec *properties.Normalization, templateTypes ...string) (str manager.AddSdkImport("github.com/PaloAltoNetworks/pango/util", "") manager.AddSdkImport("github.com/PaloAltoNetworks/pango/version", "") - if spec.Name == "global-protect-portal" && !spec.HasParametersWithStrconv() { - panic("WTF?") - } if spec.HasParametersWithStrconv() { manager.AddStandardImport("errors", "") } diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index 5499f77a..a0e782b7 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -1088,10 +1088,11 @@ type modifierCtx struct { } type validatorFunctionCtx struct { - Type string - Function string - Expressions []string - Values []string + Type string + Function string + FunctionOverriden bool + Expressions []string + Values []string } type validatorCtx struct { @@ -1375,22 +1376,35 @@ func generateValidatorFnsMapForVariants(variants []*properties.SpecParam) map[in continue } + validatorFn := "ExactlyOneOf" + var validatorFnOverride *string + if elt.TerraformProviderConfig != nil && elt.TerraformProviderConfig.VariantCheck != nil { + validatorFnOverride = elt.TerraformProviderConfig.VariantCheck + } + validator, found := validatorFns[elt.VariantGroupId] if !found { - validatorFn := "ExactlyOneOf" - if elt.TerraformProviderConfig != nil && elt.TerraformProviderConfig.VariantCheck != nil { - validatorFn = *elt.TerraformProviderConfig.VariantCheck - } - validator = &validatorFunctionCtx{ Type: "Expressions", Function: validatorFn, } + + if validatorFnOverride != nil { + validator.FunctionOverriden = true + validator.Function = *validatorFnOverride + } + } else { + if validator.FunctionOverriden { + if validatorFnOverride != nil && validator.Function != *validatorFnOverride { + panic("invalid yaml spec: parameter codegen override variant_check must be equal within variant group") + } + } else if validatorFnOverride != nil { + validator.Function = *validatorFnOverride + } } pathExpr := fmt.Sprintf(`path.MatchRelative().AtParent().AtName("%s")`, elt.TerraformNameVariant().Underscore) validator.Expressions = append(validator.Expressions, pathExpr) - validatorFns[elt.VariantGroupId] = validator } @@ -1477,7 +1491,7 @@ func createSchemaSpecForParameter(schemaTyp properties.SchemaType, manager *impo var validators *validatorCtx if schemaTyp == properties.SchemaResource { validatorFn, found := validatorFns[elt.VariantGroupId] - if found { + if found && validatorFn.Function != "Disabled" { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") @@ -1574,7 +1588,7 @@ func createSchemaSpecForParameter(schemaTyp properties.SchemaType, manager *impo var validators *validatorCtx validatorFn, found := validatorFns[elt.VariantGroupId] - if found { + if found && validatorFn.Function != "Disabled" { validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", "object") manager.AddHashicorpImport(validatorImport, "") validators = &validatorCtx{ @@ -1993,7 +2007,7 @@ func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schem var validators *validatorCtx if schemaTyp == properties.SchemaResource { validatorFn, found := validatorFns[elt.VariantGroupId] - if found { + if found && validatorFn.Function != "Disabled" { typ := elt.ValidatorType() validatorImport := fmt.Sprintf("github.com/hashicorp/terraform-plugin-framework-validators/%svalidator", typ) manager.AddHashicorpImport(validatorImport, "") From ae2f55992095efc730449b157dfeb3fc1d152aac Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Mon, 7 Jul 2025 12:56:20 +0200 Subject: [PATCH 3/7] feat(codegen): pass sdk client to custom hashing functions --- pkg/translate/terraform_provider/funcs.go | 18 +++--- pkg/translate/terraform_provider/template.go | 56 +++++++++---------- .../terraform_provider_file.go | 3 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index a0e782b7..7375fce4 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -266,9 +266,9 @@ const copyToPangoTmpl = ` return diags } {{- if eq $.Spec.ModelOrObject "Model" }} - diags.Append(object.CopyToPango(ctx, ancestors, &{{ $result }}_entry, ev)...) + diags.Append(object.CopyToPango(ctx, client, ancestors, &{{ $result }}_entry, ev)...) {{- else }} - diags.Append(object.CopyToPango(ctx, append(ancestors, o), &{{ $result }}_entry, ev)...) + diags.Append(object.CopyToPango(ctx, client, append(ancestors, o), &{{ $result }}_entry, ev)...) {{- end }} if diags.HasError() { return diags @@ -295,7 +295,7 @@ const copyToPangoTmpl = ` } for _, elt := range {{ $tfEntries }} { var entry *{{ $pangoType }} - diags.Append(elt.CopyToPango(ctx, append(ancestors, elt), &entry, ev)...) + diags.Append(elt.CopyToPango(ctx, client, append(ancestors, elt), &entry, ev)...) if diags.HasError() { return diags } @@ -341,7 +341,7 @@ const copyToPangoTmpl = ` {{- if eq .Encryption.HashingType "client" }} stateValue, found := ev.GetPlaintextValue(valueKey) if !found || stateValue != o.{{ .TerraformName.CamelCase }}.Value{{ CamelCaseType .Type }}() { - hashed, err := {{ .Encryption.HashingFunc }}(o.{{ .TerraformName.CamelCase }}.Value{{ CamelCaseType .Type }}()) + hashed, err := {{ .Encryption.HashingFunc }}(ctx, client, o.{{ .TerraformName.CamelCase }}.Value{{ CamelCaseType .Type }}()) if err != nil { diags.AddError("Failed to hash sensitive value", err.Error()) return diags @@ -378,7 +378,7 @@ const copyToPangoTmpl = ` {{- range .Specs }} {{- $spec := . }} -func (o *{{ .TerraformType }}{{ .ModelOrObject }}) CopyToPango(ctx context.Context, ancestors []Ancestor, obj **{{ .PangoReturnType }}, ev *EncryptedValuesManager) diag.Diagnostics { +func (o *{{ .TerraformType }}{{ .ModelOrObject }}) CopyToPango(ctx context.Context, client pangoutil.PangoClient, ancestors []Ancestor, obj **{{ .PangoReturnType }}, ev *EncryptedValuesManager) diag.Diagnostics { var diags diag.Diagnostics {{- range .Params }} {{- $terraformType := printf "%s%s" $spec.TerraformType .TerraformName.CamelCase }} @@ -520,7 +520,7 @@ var {{ .TerraformName.LowerCamelCase }}_list types.Set entry := {{ $terraformType }}{ Name: types.StringValue(elt.Name), } - diags.Append(entry.CopyFromPango(ctx, append(ancestors, entry), &elt, ev)...) + diags.Append(entry.CopyFromPango(ctx, client, append(ancestors, entry), &elt, ev)...) if diags.HasError() { return diags } @@ -583,9 +583,9 @@ var {{ .TerraformName.LowerCamelCase }}_list types.Set if obj.{{ .PangoName.CamelCase }} != nil { {{- if eq $.Spec.ModelOrObject "Model" }} - diags.Append({{ $result }}_obj.CopyFromPango(ctx, ancestors, obj.{{ .PangoName.CamelCase }}, ev)...) + diags.Append({{ $result }}_obj.CopyFromPango(ctx, client, ancestors, obj.{{ .PangoName.CamelCase }}, ev)...) {{- else }} - diags.Append({{ $result }}_obj.CopyFromPango(ctx, append(ancestors, o), obj.{{ .PangoName.CamelCase }}, ev)...) + diags.Append({{ $result }}_obj.CopyFromPango(ctx, client, append(ancestors, o), obj.{{ .PangoName.CamelCase }}, ev)...) {{- end }} if diags.HasError() { return diags @@ -696,7 +696,7 @@ var {{ .TerraformName.LowerCamelCase }}_list types.Set {{- range .Specs }} {{- $spec := . }} {{ $terraformType := printf "%s%s" .TerraformType .ModelOrObject }} -func (o *{{ $terraformType }}) CopyFromPango(ctx context.Context, ancestors []Ancestor, obj *{{ .PangoReturnType }}, ev *EncryptedValuesManager) diag.Diagnostics { +func (o *{{ $terraformType }}) CopyFromPango(ctx context.Context, client pangoutil.PangoClient, ancestors []Ancestor, obj *{{ .PangoReturnType }}, ev *EncryptedValuesManager) diag.Diagnostics { var diags diag.Diagnostics {{- template "terraformSetElementsAs" $spec }} diff --git a/pkg/translate/terraform_provider/template.go b/pkg/translate/terraform_provider/template.go index 8c5044f4..be075120 100644 --- a/pkg/translate/terraform_provider/template.go +++ b/pkg/translate/terraform_provider/template.go @@ -503,7 +503,7 @@ entries := make([]*{{ $resourceSDKStructName }}, len(elements)) idx := 0 for name, elt := range elements { var entry *{{ .resourceSDKName }}.{{ .EntryOrConfig }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -521,7 +521,7 @@ if resp.Diagnostics.HasError() { entries := make([]*{{ $resourceSDKStructName }}, len(elements)) for idx, elt := range elements { var entry *{{ .resourceSDKName }}.{{ .EntryOrConfig }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -549,7 +549,7 @@ for _, elt := range created { } var object {{ $resourceTFStructName }} object.name = elt.Name - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, elt, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, r.client, nil, elt, ev)...) if resp.Diagnostics.HasError() { return } @@ -575,7 +575,7 @@ for _, elt := range created { } var object {{ $resourceTFStructName }} - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, elt, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, r.client, nil, elt, ev)...) if resp.Diagnostics.HasError() { return } @@ -627,7 +627,7 @@ if resp.Diagnostics.HasError() { entries := make([]*{{ $resourceSDKStructName }}, len(elements)) for idx, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -662,7 +662,7 @@ if err != nil { objects := make([]{{ $resourceTFStructName }}, len(processed)) for idx, elt := range processed { var object {{ $resourceTFStructName }} - copy_diags := object.CopyFromPango(ctx, nil, elt, ev) + copy_diags := object.CopyFromPango(ctx, r.client, nil, elt, ev) resp.Diagnostics.Append(copy_diags...) if resp.Diagnostics.HasError() { return @@ -719,7 +719,7 @@ const resourceCreateFunction = ` // Load the desired config. var obj *{{ .resourceSDKName }}.{{ .EntryOrConfig }} - resp.Diagnostics.Append(state.CopyToPango(ctx, nil, &obj, ev)...) + resp.Diagnostics.Append(state.CopyToPango(ctx, r.client, nil, &obj, ev)...) if resp.Diagnostics.HasError() { return } @@ -763,7 +763,7 @@ const resourceCreateFunction = ` {{- end }} - resp.Diagnostics.Append(state.CopyFromPango(ctx, nil, created, ev)...) + resp.Diagnostics.Append(state.CopyFromPango(ctx, r.client, nil, created, ev)...) if resp.Diagnostics.HasError() { return } @@ -823,7 +823,7 @@ if len(elements) == 0 || resp.Diagnostics.HasError() { entries := make([]*{{ $resourceSDKStructName }}, 0, len(elements)) for name, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, o.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -840,7 +840,7 @@ if resp.Diagnostics.HasError() { entries := make([]*{{ $resourceSDKStructName }}, 0, len(elements)) for _, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, o.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -869,7 +869,7 @@ objects := make(map[string]{{ $resourceTFStructName }}) for _, elt := range readEntries { var object {{ $resourceTFStructName }} object.name = elt.Name - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, elt, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, o.client, nil, elt, ev)...) if resp.Diagnostics.HasError() { return } @@ -879,7 +879,7 @@ for _, elt := range readEntries { objects := make([]{{ $resourceTFStructName }}, len(readEntries)) for idx, elt := range readEntries { var object {{ $resourceTFStructName }} - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, elt, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, o.client, nil, elt, ev)...) if resp.Diagnostics.HasError() { return } @@ -963,7 +963,7 @@ if resp.Diagnostics.HasError() || len(elements) == 0 { entries := make([]*{{ $resourceSDKStructName }}, 0, len(elements)) for _, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, o.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1001,7 +1001,7 @@ if err != nil { var objects []{{ $resourceTFStructName }} for _, elt := range readEntries { var object {{ $resourceTFStructName }} - err := object.CopyFromPango(ctx, nil, elt, ev) + err := object.CopyFromPango(ctx, o.client, nil, elt, ev) resp.Diagnostics.Append(err...) if resp.Diagnostics.HasError() { return @@ -1077,7 +1077,7 @@ const resourceReadFunction = ` return } - copy_diags := state.CopyFromPango(ctx, nil, object, ev) + copy_diags := state.CopyFromPango(ctx, o.client, nil, object, ev) resp.Diagnostics.Append(copy_diags...) /* @@ -1135,7 +1135,7 @@ stateEntries := make([]*{{ $resourceSDKStructName }}, len(elements)) idx := 0 for name, elt := range elements { var entry *{{ .resourceSDKName }}.{{ .EntryOrConfig }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1153,7 +1153,7 @@ if resp.Diagnostics.HasError() { stateEntries := make([]*{{ $resourceSDKStructName }}, len(elements)) for idx, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1188,7 +1188,7 @@ planEntries := make([]*{{ $resourceSDKStructName }}, len(elements)) idx = 0 for name, elt := range elements { entry, _ := existingEntriesByName[name] - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1201,7 +1201,7 @@ for name, elt := range elements { var planEntries []*{{ $resourceSDKStructName }} for _, elt := range elements { existingEntry, _ := existingEntriesByName[elt.Name.ValueString()] - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &existingEntry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &existingEntry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1221,7 +1221,7 @@ objects := make(map[string]*{{ $resourceTFStructName }}, len(processed)) for _, elt := range processed { var object {{ $resourceTFStructName }} object.name = elt.Name - copy_diags := object.CopyFromPango(ctx, nil, elt, ev) + copy_diags := object.CopyFromPango(ctx, r.client, nil, elt, ev) resp.Diagnostics.Append(copy_diags...) if resp.Diagnostics.HasError() { return @@ -1232,7 +1232,7 @@ for _, elt := range processed { objects := make([]*{{ $resourceTFStructName }}, len(processed)) for idx, elt := range processed { var object {{ $resourceTFStructName }} - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, elt, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, r.client, nil, elt, ev)...) if resp.Diagnostics.HasError() { return } @@ -1289,7 +1289,7 @@ if resp.Diagnostics.HasError() { stateEntries := make([]*{{ $resourceSDKStructName }}, len(elements)) for idx, elt := range elements { var entry *{{ $resourceSDKStructName }} - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1328,7 +1328,7 @@ if resp.Diagnostics.HasError() { planEntries := make([]*{{ $resourceSDKStructName }}, len(elements)) for idx, elt := range elements { entry, _ := existingEntriesByName[elt.Name.ValueString()] - resp.Diagnostics.Append(elt.CopyToPango(ctx, nil, &entry, ev)...) + resp.Diagnostics.Append(elt.CopyToPango(ctx, r.client, nil, &entry, ev)...) if resp.Diagnostics.HasError() { return } @@ -1349,7 +1349,7 @@ if err != nil { objects := make([]*{{ $resourceTFStructName }}, len(processed)) for idx, elt := range processed { var object {{ $resourceTFStructName }} - copy_diags := object.CopyFromPango(ctx, nil, elt, ev) + copy_diags := object.CopyFromPango(ctx, r.client, nil, elt, ev) resp.Diagnostics.Append(copy_diags...) if resp.Diagnostics.HasError() { return @@ -1412,7 +1412,7 @@ const resourceUpdateFunction = ` return } - resp.Diagnostics.Append(plan.CopyToPango(ctx, nil, &obj, ev)...) + resp.Diagnostics.Append(plan.CopyToPango(ctx, r.client, nil, &obj, ev)...) if resp.Diagnostics.HasError() { return } @@ -1442,7 +1442,7 @@ const resourceUpdateFunction = ` */ - copy_diags := state.CopyFromPango(ctx, nil, updated, ev) + copy_diags := state.CopyFromPango(ctx, r.client, nil, updated, ev) resp.Diagnostics.Append(copy_diags...) if resp.Diagnostics.HasError() { return @@ -1940,7 +1940,7 @@ const resourceImportStateFunctionTmpl = ` } for _, elt := range objectNames { object := &{{ .ListStructName }}{} - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, &{{ .PangoStructName }}{}, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, r.client, nil, &{{ .PangoStructName }}{}, ev)...) if resp.Diagnostics.HasError() { return } @@ -1963,7 +1963,7 @@ const resourceImportStateFunctionTmpl = ` } for _, elt := range objectNames { object := &{{ .ListStructName }}{} - resp.Diagnostics.Append(object.CopyFromPango(ctx, nil, &{{ .PangoStructName }}{}, ev)...) + resp.Diagnostics.Append(object.CopyFromPango(ctx, r.client, nil, &{{ .PangoStructName }}{}, ev)...) if resp.Diagnostics.HasError() { return } diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index c531ac23..b9d71a69 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -255,10 +255,10 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper case properties.TerraformResourceEntry, properties.TerraformResourceUuid: terraformProvider.ImportManager.AddStandardImport("encoding/base64", "") terraformProvider.ImportManager.AddOtherImport("github.com/PaloAltoNetworks/terraform-provider-panos/internal/manager", "sdkmanager") + terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango/util", "pangoutil") case properties.TerraformResourceConfig: terraformProvider.ImportManager.AddOtherImport("github.com/PaloAltoNetworks/terraform-provider-panos/internal/manager", "sdkmanager") case properties.TerraformResourceCustom: - } // entry or uuid style resource @@ -388,6 +388,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop if spec.TerraformProviderConfig.ResourceType != properties.TerraformResourceCustom { terraformProvider.ImportManager.AddStandardImport("errors", "") terraformProvider.ImportManager.AddOtherImport("github.com/PaloAltoNetworks/terraform-provider-panos/internal/manager", "sdkmanager") + terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango/util", "pangoutil") } terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango", "") From 38e4213d9c07628094ad6c1726cff72ae151ceef Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Tue, 8 Jul 2025 10:28:04 +0200 Subject: [PATCH 4/7] fix(tests): use template stack location for template variable meant for stack --- .../terraform/test/resource_panorama_template_variable_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/terraform/test/resource_panorama_template_variable_test.go b/assets/terraform/test/resource_panorama_template_variable_test.go index 51596b52..4a41eb8a 100644 --- a/assets/terraform/test/resource_panorama_template_variable_test.go +++ b/assets/terraform/test/resource_panorama_template_variable_test.go @@ -160,7 +160,7 @@ resource "panos_template_variable" "tmpl-var" { } resource "panos_template_variable" "stack-var" { - location = { template = { name = panos_template_stack.example.name } } + location = { template_stack = { name = panos_template_stack.example.name } } name = format("$%s", var.prefix) type = { interface = "ethernet1/1" } From 8078a445b0d250bc2be78c606ff4d3be0e224953 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Tue, 8 Jul 2025 12:37:37 +0200 Subject: [PATCH 5/7] feat(codegen): Add a way to configure codegen log level and hide most output behind debug level --- cmd/codegen/main.go | 22 +++++++++++++++++-- pkg/commands/codegen/codegen.go | 10 ++++----- pkg/generate/assets.go | 6 ++--- pkg/generate/generator.go | 5 ++--- pkg/properties/normalized.go | 1 - pkg/translate/structs.go | 3 --- pkg/translate/terraform_provider/funcs.go | 1 + .../terraform_provider_file.go | 2 +- 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index d4395d1a..9863fff2 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -3,7 +3,10 @@ package main import ( "context" "flag" + "fmt" "log" + "log/slog" + "os" "github.com/paloaltonetworks/pan-os-codegen/pkg/commands/codegen" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" @@ -40,6 +43,22 @@ func runCommand(ctx context.Context, cmdType properties.CommandType, cfg string) } func main() { + logLevel := os.Getenv("CODEGEN_LOG_LEVEL") + if logLevel == "" { + logLevel = "ERROR" + } + var level slog.Level + var err = level.UnmarshalText([]byte(logLevel)) + if err != nil { + fmt.Print(err.Error()) + return + } + + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: level, + })) + slog.SetDefault(logger) + cfg := parseFlags() ctx := context.Background() @@ -52,8 +71,7 @@ func main() { } else { opTypeMessage += cfg.OpType } - log.Printf("Using configuration file: %s\n", cfg.ConfigFile) - log.Println(opTypeMessage) + slog.Debug("Parsed configuration file", "path", cfg.ConfigFile) cmdType := properties.CommandTypeSDK // Default command type if cfg.OpType == "mktp" { diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index 9d4184ac..2fbcf69a 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -3,7 +3,7 @@ package codegen import ( "context" "fmt" - "log" + "log/slog" "github.com/paloaltonetworks/pan-os-codegen/pkg/generate" "github.com/paloaltonetworks/pan-os-codegen/pkg/load" @@ -56,13 +56,13 @@ func (c *Command) Setup() error { } func (c *Command) Execute() error { - log.Printf("Generating %s\n", c.commandType) - if len(c.args) == 0 { return fmt.Errorf("path to configuration file is required") } configPath := c.args[0] + slog.Info("Generating code", "type", c.commandType) + content, err := load.File(configPath) if err != nil { return fmt.Errorf("error loading %s - %s", configPath, err) @@ -78,7 +78,7 @@ func (c *Command) Execute() error { specMetadata := make(map[string]properties.TerraformProviderSpecMetadata) for _, specPath := range c.specs { - log.Printf("Parsing %s...\n", specPath) + slog.Info("Parsing YAML spec", "spec", specPath) content, err := load.File(specPath) if err != nil { return fmt.Errorf("error loading %s - %s", specPath, err) @@ -194,7 +194,7 @@ func (c *Command) Execute() error { if err != nil { return fmt.Errorf("error generating terraform code: %w", err) } - log.Printf("Generated Terraform resources: %s\n Generated dataSources: %s", resourceList, dataSourceList) + slog.Debug("Generated Terraform resources", "resources", resourceList, "dataSources", dataSourceList) } if err = generate.CopyAssets(config, c.commandType); err != nil { diff --git a/pkg/generate/assets.go b/pkg/generate/assets.go index 58f6e315..63b94ed7 100644 --- a/pkg/generate/assets.go +++ b/pkg/generate/assets.go @@ -4,7 +4,7 @@ import ( "bytes" "io" "io/fs" - "log" + "log/slog" "os" "path/filepath" "strings" @@ -20,7 +20,7 @@ func CopyAssets(config *properties.Config, commandType properties.CommandType) e return err } - log.Printf("%v", asset) + slog.Debug("Copying static assets", "source", asset) if asset.Target.GoSdk && commandType == properties.CommandTypeSDK { if err = copyAsset(config.Output.GoSdk, asset, files); err != nil { return err @@ -80,7 +80,7 @@ func copyAsset(target string, asset *properties.Asset, files []string) error { } destinationFilePath := filepath.Join(destinationDir, sourceFileDirRelative, filepath.Base(sourceFilePath)) - log.Printf("Copy file from %s to %s\n", sourceFilePath, destinationFilePath) + slog.Debug("Copying rendered file", "source", sourceFilePath, "destination", destinationFilePath) // Read the contents of the source files data, err := os.ReadFile(sourceFilePath) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 16feec3d..d43b10fd 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -7,6 +7,7 @@ import ( "go/format" "io" "log" + "log/slog" "os" "path/filepath" "strings" @@ -37,8 +38,6 @@ func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization // RenderTemplate loops through all templates, parses them, and renders content, which is saved to the output file. func (c *Creator) RenderTemplate() error { - log.Println("Start rendering templates") - templates, err := c.listOfTemplates() if err != nil { return fmt.Errorf("error listing templates: %w", err) @@ -46,7 +45,7 @@ func (c *Creator) RenderTemplate() error { for _, templateName := range templates { filePath := c.createFullFilePath(templateName) - log.Printf("Creating file: %s\n", filePath) + slog.Debug("Creating target file", "path", filePath) if err := c.makeAllDirs(filePath); err != nil { return fmt.Errorf("error creating directories for %s: %w", filePath, err) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index a54a65ef..0cfc6c4c 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -1578,7 +1578,6 @@ func (spec *Normalization) HasPrivateParameters() bool { } func resolveXpath(xpath []string, spec *Spec) (*SpecParam, error) { - fmt.Printf("xpath[0]: %s\n", xpath[0]) elt := xpath[0] if elt == "spec" { elt = xpath[1] diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index eca8b665..3b18f13e 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -3,7 +3,6 @@ package translate import ( "bytes" "fmt" - "log" "strings" "text/template" @@ -486,8 +485,6 @@ func checkIfDeviceVersionSupportedByProfile(param *properties.SpecParam, deviceV return true } - log.Printf("Param: %s, deviceVersion: %s, MinVersion: %s, MaxVersion: %s", param.Name.CamelCase, deviceVersion, profile.MinVersion.String(), profile.MaxVersion.String()) - if deviceVersion.GreaterThanOrEqualTo(*profile.MinVersion) && deviceVersion.LesserThan(*profile.MaxVersion) { return true } diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index 7375fce4..ef13e219 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -3,6 +3,7 @@ package terraform_provider import ( "fmt" "log" + "log/slog" "runtime/debug" "sort" "strings" diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index b9d71a69..ff4c8810 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -45,7 +45,7 @@ func (g *GenerateTerraformProvider) updateProviderFile(spec *properties.Normaliz if schemaTyp == properties.SchemaProvider { terraformProvider.Code = renderedTemplate } else { - log.Printf("updateProviderFile() renderedTemplate: %d", renderedTemplate.Len()) + slog.Debug("updateProviderFile() renderedTemplate", "renderedTemplate.Len()", renderedTemplate.Len()) if _, err := terraformProvider.Code.WriteString(renderedTemplate.String()); err != nil { return fmt.Errorf("error writing %v template: %v", resourceTyp, err) } From 8bd6ad7bb73f5a591395e55c30ba722a44d99266 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Wed, 9 Jul 2025 11:57:36 +0200 Subject: [PATCH 6/7] feat(codegen): Support overriding of terraform parameter optional value --- pkg/properties/normalized.go | 10 ++++++++++ pkg/schema/parameter/parameter.go | 1 + pkg/translate/terraform_provider/funcs.go | 13 +++++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 0cfc6c4c..28e41f24 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -201,6 +201,7 @@ type SpecParamTerraformProviderConfig struct { Type *string `json:"type" yaml:"type"` Private *bool `json:"ignored" yaml:"private"` Sensitive *bool `json:"sensitive" yaml:"sensitive"` + Optional *bool `json:"optional" yaml:"optional"` Computed *bool `json:"computed" yaml:"computed"` Required *bool `json:"required" yaml:"required"` VariantCheck *string `json:"variant_check" yaml:"variant_check"` @@ -321,6 +322,14 @@ func (o *SpecParam) FinalSensitive() bool { return false } +func (o *SpecParam) FinalOptional() bool { + if o.TerraformProviderConfig != nil && o.TerraformProviderConfig.Optional != nil { + return *o.TerraformProviderConfig.Optional + } + + return !o.FinalRequired() +} + func (o *SpecParam) FinalComputed() bool { if o.TerraformProviderConfig != nil && o.TerraformProviderConfig.Computed != nil { return *o.TerraformProviderConfig.Computed @@ -590,6 +599,7 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam Name: schemaSpec.CodegenOverrides.Terraform.Name, Type: schemaSpec.CodegenOverrides.Terraform.Type, Private: schemaSpec.CodegenOverrides.Terraform.Private, + Optional: schemaSpec.CodegenOverrides.Terraform.Optional, Sensitive: schemaSpec.CodegenOverrides.Terraform.Sensitive, Computed: schemaSpec.CodegenOverrides.Terraform.Computed, Required: schemaSpec.CodegenOverrides.Terraform.Required, diff --git a/pkg/schema/parameter/parameter.go b/pkg/schema/parameter/parameter.go index 97823ace..f25079ff 100644 --- a/pkg/schema/parameter/parameter.go +++ b/pkg/schema/parameter/parameter.go @@ -61,6 +61,7 @@ type CodegenOverridesTerraform struct { Name *string `yaml:"name"` Type *string `yaml:"type"` Private *bool `yaml:"private"` + Optional *bool `yaml:"optional"` Sensitive *bool `yaml:"sensitive"` Computed *bool `yaml:"computed"` Required *bool `yaml:"required"` diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index ef13e219..1cc50bdc 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -1029,7 +1029,8 @@ const locationSchemaGetterTmpl = ` Description: "{{ .Description }}", {{- if .Required }} Required: true, - {{- else }} + {{- end }} + {{- if .Optional }} Optional: true, {{- end }} {{- if .Computed }} @@ -1190,6 +1191,7 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat Name: name, Description: variable.Description, SchemaType: "rsschema.StringAttribute", + Optional: true, Required: false, Computed: true, Default: &defaultCtx{ @@ -1230,6 +1232,7 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat Name: data.Name, SchemaType: "rsschema.SingleNestedAttribute", Description: data.Description, + Optional: true, Required: false, Attributes: variableAttrs, ModifierType: modifierType, @@ -1535,7 +1538,7 @@ func createSchemaSpecForParameter(schemaTyp properties.SchemaType, manager *impo ReturnType: returnType, Description: "", Required: required, - Optional: !param.FinalRequired(), + Optional: param.FinalOptional(), Computed: computed, Sensitive: param.FinalSensitive(), Attributes: attributes, @@ -1660,12 +1663,14 @@ func createSchemaAttributeForParameter(schemaTyp properties.SchemaType, manager } } - var computed, required bool + var computed, required, optional bool switch schemaTyp { case properties.SchemaDataSource: + optional = true required = false computed = true case properties.SchemaResource, properties.SchemaEphemeralResource: + optional = param.FinalOptional() computed = param.FinalComputed() required = param.FinalRequired() case properties.SchemaCommon, properties.SchemaProvider: @@ -1679,7 +1684,7 @@ func createSchemaAttributeForParameter(schemaTyp properties.SchemaType, manager ElementType: elementType, Description: param.Description, Required: required, - Optional: !required, + Optional: optional, Sensitive: param.FinalSensitive(), Default: defaultValue, Computed: computed, From 82b2d2512ed2b124aef50f1efeb28a7fd809e00b Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Thu, 29 May 2025 14:27:34 +0200 Subject: [PATCH 7/7] feat(specs): Add spec, tests and examples for device administrators --- .../test/resource_administrators_test.go | 676 +++++++++++++ specs/device/administrators.yaml | 896 ++++++++++++++++++ 2 files changed, 1572 insertions(+) create mode 100644 assets/terraform/test/resource_administrators_test.go create mode 100644 specs/device/administrators.yaml diff --git a/assets/terraform/test/resource_administrators_test.go b/assets/terraform/test/resource_administrators_test.go new file mode 100644 index 00000000..f17b7a56 --- /dev/null +++ b/assets/terraform/test/resource_administrators_test.go @@ -0,0 +1,676 @@ +package provider_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + //"github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccAdministrator_Password_Hashing(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_Password_Hashing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + "password": config.StringVariable("initial"), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("name"), + knownvalue.StringExact(prefix), + ), + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("password"), + knownvalue.StringExact("initial"), + ), + }, + }, + { + Config: panosAdministrators_Password_Hashing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + "password": config.StringVariable("updated"), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("name"), + knownvalue.StringExact(prefix), + ), + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("password"), + knownvalue.StringExact("updated"), + ), + }, + }, + }, + }) +} + +const panosAdministrators_Password_Hashing_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } +variable "password" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = { panorama = {} } + + name = var.prefix + + password = var.password +} +` + +func TestAccAdministrator_Basic(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_Basic_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("name"), + knownvalue.StringExact(prefix), + ), + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("client_certificate_only"), + knownvalue.Bool(false), + ), + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("preferences").AtMapKey("disable_dns"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("preferences").AtMapKey("saved_log_query").AtMapKey("traffic").AtSliceIndex(0), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("Example Query"), + "query": knownvalue.StringExact("addr.src in 10.0.0.0/8"), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_Basic_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + + name = var.prefix + password = "admin123" + + client_certificate_only = false + + preferences = { + disable_dns = true + saved_log_query = { + traffic = [ + { + name = "Example Query" + query = "addr.src in 10.0.0.0/8" + } + ] + } + } +} +` + +func TestAccAdministrator_RoleBased_Custom(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_Custom_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("custom"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "profile": knownvalue.StringExact(prefix), + "vsys": knownvalue.Null(), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_Custom_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + +resource "panos_admin_role" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + role = { + vsys = { + cli = "vsysreader" + } + } +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + custom = { + profile = panos_admin_role.example.name + } + } + } +} +` + +func TestAccAdministrator_RoleBased_DeviceAdmin(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_DeviceAdmin_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("deviceadmin"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("device1"), + knownvalue.StringExact("device2"), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_DeviceAdmin_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + deviceadmin = ["device1", "device2"] + } + } +} +` + +func TestAccAdministrator_RoleBased_DeviceReader(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_DeviceReader_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("devicereader"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("device1"), + knownvalue.StringExact("device2"), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_DeviceReader_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + devicereader = ["device1", "device2"] + } + } +} +` + +func TestAccAdministrator_RoleBased_PanoramaAdmin(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_PanoramaAdmin_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("panorama_admin"), + knownvalue.StringExact("yes"), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_PanoramaAdmin_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + panorama_admin = "yes" + } + } +} +` + +func TestAccAdministrator_RoleBased_SuperReader(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_SuperReader_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("superreader"), + knownvalue.StringExact("yes"), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_SuperReader_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + superreader = "yes" + } + } +} +` + +func TestAccAdministrator_RoleBased_SuperUser(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_SuperUser_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("superuser"), + knownvalue.StringExact("yes"), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_SuperUser_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" example { + location = { panorama = {} } + name = var.prefix +} + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + superuser = "yes" + } + } +} +` + +func TestAccAdministrator_RoleBased_VsysAdmin(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "panorama": config.ObjectVariable(map[string]config.Variable{}), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_VsysAdmin_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("vsysadmin"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("vsys_admin1"), + "vsys": knownvalue.Null(), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_VsysAdmin_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" "example" { + location = { panorama = {} } + name = var.prefix +} + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + vsysadmin = [ + { + name = "vsys_admin1" + } + ] + } + } +} +` + +func TestAccAdministrator_RoleBased_VsysReader(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + location := config.ObjectVariable(map[string]config.Variable{ + "template": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable(prefix), + }), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: panosAdministrators_RoleBased_VsysReader_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "location": location, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_administrator.example", + tfjsonpath.New("permissions").AtMapKey("role_based").AtMapKey("vsysreader").AtSliceIndex(0), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("vsys_reader1"), + "vsys": knownvalue.Null(), + }), + ), + }, + }, + }, + }) +} + +const panosAdministrators_RoleBased_VsysReader_Tmpl = ` +variable "location" { type = any } +variable "prefix" { type = string } + +resource "panos_template" "example" { + location = { panorama = {} } + + name = var.prefix +} + + +resource "panos_administrator" "example" { + depends_on = [panos_template.example] + location = var.location + name = var.prefix + password = "admin123" + + permissions = { + role_based = { + vsysreader = [ + { + name = "vsys_reader1" + } + ] + } + } +} +` diff --git a/specs/device/administrators.yaml b/specs/device/administrators.yaml new file mode 100644 index 00000000..5501c5f9 --- /dev/null +++ b/specs/device/administrators.yaml @@ -0,0 +1,896 @@ +name: administrators +terraform_provider_config: + description: Administrator + skip_resource: false + skip_datasource: false + resource_type: entry + resource_variants: + - singular + suffix: administrator + plural_suffix: '' + plural_name: '' + plural_description: '' +go_sdk_config: + skip: false + package: + - device + - administrators +panos_xpath: + path: + - users + vars: [] +locations: +- name: panorama + xpath: + path: + - config + - mgt-config + vars: [] + description: Located in panorama. + validators: [] + required: false + read_only: false +- name: ngfw + xpath: + path: + - config + - mgt-config + vars: [] + description: Located within device-level configuration + devices: + - ngfw + validators: [] + required: false + read_only: false +- name: template + xpath: + path: + - config + - devices + - $panorama_device + - template + - $template + - config + - mgt-config + vars: + - name: panorama_device + description: Specific Panorama device + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: template + description: Specific Panorama template + required: true + validators: [] + type: entry + description: A management configuration object located within a specific template + devices: + - panorama + validators: [] + required: false + read_only: false +- name: template-stack + xpath: + path: + - config + - devices + - $panorama_device + - template-stack + - $template_stack + - config + - mgt-config + vars: + - name: panorama_device + description: Specific Panorama device + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: template_stack + description: The template stack + required: true + validators: [] + type: entry + description: A management configuration object located within a specific template + devices: + - panorama + validators: [] + required: false + read_only: false +entries: +- name: name + description: '' + validators: [] +imports: [] +spec: + params: + - name: authentication-profile + type: string + profiles: + - xpath: + - authentication-profile + validators: [] + spec: {} + description: '' + required: false + - name: client-certificate-only + type: bool + profiles: + - xpath: + - client-certificate-only + validators: [] + spec: {} + description: Is client certificate authentication enough? + required: false + - name: password-profile + type: string + profiles: + - xpath: + - password-profile + validators: [] + spec: {} + description: '' + required: false + - name: permissions + type: object + profiles: + - xpath: + - permissions + validators: [] + spec: + params: [] + variants: + - name: role-based + type: object + profiles: + - xpath: + - role-based + validators: [] + spec: + params: [] + variants: + - name: custom + type: object + profiles: + - xpath: + - custom + validators: [] + spec: + params: + - name: profile + type: string + profiles: + - xpath: + - profile + validators: + - type: length + spec: + max: 63 + spec: {} + description: '' + required: false + - name: vsys + type: list + profiles: + - xpath: + - vsys + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + codegen_overrides: + terraform: + optional: false + computed: true + variants: [] + description: '' + required: false + - name: deviceadmin + type: list + profiles: + - xpath: + - deviceadmin + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + - name: devicereader + type: list + profiles: + - xpath: + - devicereader + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + - name: panorama-admin + type: enum + profiles: + - xpath: + - panorama-admin + validators: + - type: values + spec: + values: + - 'yes' + spec: + values: + - value: 'yes' + description: '' + required: false + - name: superreader + type: enum + profiles: + - xpath: + - superreader + validators: + - type: values + spec: + values: + - 'yes' + spec: + values: + - value: 'yes' + description: '' + required: false + - name: superuser + type: enum + profiles: + - xpath: + - superuser + validators: + - type: values + spec: + values: + - 'yes' + spec: + values: + - value: 'yes' + description: '' + required: false + - name: vsysadmin + type: list + profiles: + - xpath: + - vsysadmin + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: vsys + type: list + profiles: + - xpath: + - vsys + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + codegen_overrides: + terraform: + optional: false + computed: true + variants: [] + description: '' + required: false + - name: vsysreader + type: list + profiles: + - xpath: + - vsysreader + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: vsys + type: list + profiles: + - xpath: + - vsys + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + codegen_overrides: + terraform: + optional: false + computed: true + variants: [] + description: '' + required: false + codegen_overrides: + terraform: + variant_check: Disabled + description: '' + required: false + description: '' + required: false + - name: phash + type: string + profiles: + - xpath: + - phash + validators: + - type: length + spec: + max: 63 + spec: {} + description: '' + required: false + codegen_overrides: + terraform: + name: password + hashing: + type: client + spec: + hashing_func: + name: administratorCreatePasswordHash + - name: preferences + type: object + profiles: + - xpath: + - preferences + validators: [] + spec: + params: + - name: disable-dns + type: bool + profiles: + - xpath: + - disable-dns + validators: [] + spec: {} + description: '' + required: false + - name: saved-log-query + type: object + profiles: + - xpath: + - saved-log-query + validators: [] + spec: + params: + - name: alarm + type: list + profiles: + - xpath: + - alarm + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: auth + type: list + profiles: + - xpath: + - auth + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: config + type: list + profiles: + - xpath: + - config + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: corr + type: list + profiles: + - xpath: + - corr + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: data + type: list + profiles: + - xpath: + - data + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: decryption + type: list + profiles: + - xpath: + - decryption + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: globalprotect + type: list + profiles: + - xpath: + - globalprotect + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: gtp + type: list + profiles: + - xpath: + - gtp + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: hipmatch + type: list + profiles: + - xpath: + - hipmatch + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: system + type: list + profiles: + - xpath: + - system + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: threat + type: list + profiles: + - xpath: + - threat + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: traffic + type: list + profiles: + - xpath: + - traffic + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: tunnel + type: list + profiles: + - xpath: + - tunnel + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: unified + type: list + profiles: + - xpath: + - unified + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: url + type: list + profiles: + - xpath: + - url + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: userid + type: list + profiles: + - xpath: + - userid + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: wildfire + type: list + profiles: + - xpath: + - wildfire + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: query + type: string + profiles: + - xpath: + - query + validators: + - type: length + spec: + max: 2048 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + - name: public-key + type: string + profiles: + - xpath: + - public-key + validators: + - type: length + spec: + min: 1 + max: 4096 + spec: {} + description: Public RSA/DSA + required: false + variants: []