Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Replace hand-written specs with autogenerated specs #186

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion assets/terraform/test/resource_ethernet_interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func makePanosEthernetInterface_Layer3(label string) string {
{
advertise = {
enable = true
valid_lifetime = "10000"
valid_lifetime = "1000000"
},
name = "::1",
enable_on_interface = true
Expand Down
1 change: 1 addition & 0 deletions assets/terraform/test/resource_nat_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ resource "panos_nat_policy" "{{ .ResourceName }}" {
rules = [
for index, name in var.rule_names: {
name = name

source_zones = ["any"]
source_addresses = ["any"]
destination_zone = ["external"]
Expand Down
2 changes: 1 addition & 1 deletion assets/terraform/test/resource_virtual_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func makePanosVirtualRouterConfig(label string) string {
{
advertise = {
enable = true
valid_lifetime = "10000"
valid_lifetime = "1000000"
},
name = "::1",
enable_on_interface = true
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (c *Command) Execute() error {
case properties.TerraformResourceCustom:
resourceTyp = properties.ResourceCustom
case properties.TerraformResourceConfig:
panic("missing implementation for config type resources")
resourceTyp = properties.ResourceConfig
}

terraformGenerator := generate.NewCreator(config.Output.TerraformProvider, c.templatePath, spec)
Expand Down
12 changes: 7 additions & 5 deletions pkg/generate/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"bytes"
"errors"
"fmt"
"go/format"
"io"
Expand Down Expand Up @@ -59,7 +60,7 @@
// RenderTerraformProviderFile generates a Go file for a Terraform provider based on the provided TerraformProviderFile and Normalization arguments.
func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, typ properties.ResourceType) ([]string, []string, map[string]properties.TerraformProviderSpecMetadata, error) {
var name string
switch typ {

Check failure on line 63 in pkg/generate/generator.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type properties.ResourceType: properties.ResourceConfig (exhaustive)
case properties.ResourceUuidPlural:
name = fmt.Sprintf("%s_%s", spec.TerraformProviderConfig.Suffix, spec.TerraformProviderConfig.PluralName)
case properties.ResourceEntryPlural:
Expand Down Expand Up @@ -95,7 +96,7 @@
case properties.ResourceEntryPlural:
name = spec.TerraformProviderConfig.PluralSuffix
filePath = c.createTerraformProviderFilePath(name)
case properties.ResourceEntry, properties.ResourceUuid, properties.ResourceCustom:
case properties.ResourceEntry, properties.ResourceUuid, properties.ResourceCustom, properties.ResourceConfig:
filePath = c.createTerraformProviderFilePath(spec.TerraformProviderConfig.Suffix)
}

Expand Down Expand Up @@ -140,8 +141,9 @@
}
formattedBuf := bytes.NewBuffer(formattedCode)

if writeErr := c.createAndWriteFile(filePath, formattedBuf); writeErr != nil {
return fmt.Errorf("error creating and writing to file %s: %w", filePath, err)
writeErr := c.createAndWriteFile(filePath, formattedBuf)
if writeErr != nil {
return errors.Join(err, writeErr)
}
}
return err
Expand All @@ -151,8 +153,8 @@
func (c *Creator) writeFormattedContentToFile(filePath, content string) error {
formattedCode, err := format.Source([]byte(content))
if err != nil {
log.Printf("provided content: %s", content)
return fmt.Errorf("error formatting code: %w", err)
log.Printf("failed to format provided content: %s", filePath)
formattedCode = []byte(content)
}
formattedBuf := bytes.NewBuffer(formattedCode)

Expand Down
36 changes: 26 additions & 10 deletions pkg/properties/normalized.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package properties
import (
"fmt"
"io/fs"
"log"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -174,11 +175,13 @@ type SpecParam struct {
}

type SpecParamTerraformProviderConfig struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Private bool `json:"ignored" yaml:"private"`
Sensitive bool `json:"sensitive" yaml:"sensitive"`
Computed bool `json:"computed" yaml:"computed"`
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Private bool `json:"ignored" yaml:"private"`
Sensitive bool `json:"sensitive" yaml:"sensitive"`
Computed bool `json:"computed" yaml:"computed"`
Required *bool `json:"required" yaml:"required"`
VariantCheck string `json:"variant_check" yaml:"variant_check"`
}

type SpecParamLength struct {
Expand Down Expand Up @@ -221,6 +224,14 @@ func (o *SpecParam) NameVariant() *NameVariant {
return o.Name
}

func (o *SpecParam) FinalRequired() bool {
if o.TerraformProviderConfig != nil && o.TerraformProviderConfig.Required != nil {
return *o.TerraformProviderConfig.Required
}

return o.Required
}

func hasChildEncryptedResources(param *SpecParam) bool {
if param.Hashing != nil {
return true
Expand Down Expand Up @@ -404,6 +415,8 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam
if err != nil {
return nil, err
}
} else if spec.Items.Type == "enum" {
itemsSpec.Type = "string"
} else {
itemsSpec.Type = spec.Items.Type
}
Expand Down Expand Up @@ -459,12 +472,15 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam
if schemaSpec.CodegenOverrides != nil {
sensitive = schemaSpec.CodegenOverrides.Terraform.Sensitive
terraformProviderConfig = &SpecParamTerraformProviderConfig{
Name: schemaSpec.CodegenOverrides.Terraform.Name,
Type: schemaSpec.CodegenOverrides.Terraform.Type,
Private: schemaSpec.CodegenOverrides.Terraform.Private,
Sensitive: schemaSpec.CodegenOverrides.Terraform.Sensitive,
Computed: schemaSpec.CodegenOverrides.Terraform.Computed,
Name: schemaSpec.CodegenOverrides.Terraform.Name,
Type: schemaSpec.CodegenOverrides.Terraform.Type,
Private: schemaSpec.CodegenOverrides.Terraform.Private,
Sensitive: schemaSpec.CodegenOverrides.Terraform.Sensitive,
Computed: schemaSpec.CodegenOverrides.Terraform.Computed,
Required: schemaSpec.CodegenOverrides.Terraform.Required,
VariantCheck: string(schemaSpec.CodegenOverrides.Terraform.VariantCheck),
}
log.Printf("terraformProviderConfig: %s VariantCheck: '%s'\n", schemaSpec.Name, terraformProviderConfig.VariantCheck)
}
specParameter := &SpecParam{
Description: schemaSpec.Description,
Expand Down
2 changes: 1 addition & 1 deletion pkg/properties/normalized_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestUnmarshallAddressSpecFile(t *testing.T) {

// then
assert.NotNilf(t, yamlParsedData, "Unmarshalled data cannot be nil")
assert.Equal(t, "Address", yamlParsedData.Name, "Unmarshalled data should contain `name`")
assert.Equal(t, "address", yamlParsedData.Name, "Unmarshalled data should contain `name`")
assert.NotNilf(t, yamlParsedData.TerraformProviderConfig.Suffix, "Unmarshalled data should contain `suffix`")
assert.NotNilf(t, yamlParsedData.GoSdkPath, "Unmarshalled data should contain `go_sdk_path`")
assert.NotNilf(t, yamlParsedData.XpathSuffix, "Unmarshalled data should contain `xpath_suffix`")
Expand Down
2 changes: 1 addition & 1 deletion pkg/properties/provider_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type TerraformNameProvider struct {
func NewTerraformNameProvider(spec *Normalization, resourceTyp ResourceType) *TerraformNameProvider {
var tfName string
switch resourceTyp {
case ResourceEntry, ResourceCustom:
case ResourceEntry, ResourceCustom, ResourceConfig:
tfName = spec.TerraformProviderConfig.Suffix
case ResourceEntryPlural:
tfName = spec.TerraformProviderConfig.PluralSuffix
Expand Down
11 changes: 6 additions & 5 deletions pkg/properties/resourcetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package properties
type ResourceType int

const (
ResourceEntry ResourceType = iota
ResourceCustom ResourceType = iota
ResourceEntryPlural ResourceType = iota
ResourceUuid ResourceType = iota
ResourceUuidPlural ResourceType = iota
ResourceEntry ResourceType = iota
ResourceCustom
ResourceConfig
ResourceEntryPlural
ResourceUuid
ResourceUuidPlural
)

type SchemaType int
Expand Down
21 changes: 15 additions & 6 deletions pkg/schema/parameter/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,21 @@ type EnumSpecValue struct {
Const string `yaml:"const"`
}

type VariantCheckType string

const (
VariantCheckConflictsWith VariantCheckType = "ConflictsWith"
VariantCheckExactlyOneOf VariantCheckType = "ExactlyOneOf"
)

type CodegenOverridesTerraform struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Private bool `yaml:"private"`
Sensitive bool `yaml:"sensitive"`
Computed bool `yaml:"computed"`
Name string `yaml:"name"`
Type string `yaml:"type"`
Private bool `yaml:"private"`
Sensitive bool `yaml:"sensitive"`
Computed bool `yaml:"computed"`
Required *bool `yaml:"required"`
VariantCheck VariantCheckType `yaml:"variant_check"`
}

type CodegenOverrides struct {
Expand Down Expand Up @@ -133,7 +142,7 @@ func (p *Parameter) UnmarshalYAML(n *yaml.Node) error {
p.Spec = new(EnumSpec)
case "nil":
p.Spec = new(NilSpec)
case "string", "bool", "int64":
case "string", "bool", "int64", "float64":
p.Spec = new(SimpleSpec)
default:
return errors.NewSchemaError(fmt.Sprintf("unsupported parameter type: '%s'", p.Type))
Expand Down
49 changes: 32 additions & 17 deletions pkg/translate/assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,24 +133,27 @@ func defineNestedObject(parent []*properties.SpecParam, param, parentParam *prop
}
}

func startIfBlockForParamNotNil(parent []*properties.SpecParam, param *properties.SpecParam, parentParam *properties.SpecParam, builder *strings.Builder) {
if len(parent) == 2 && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
func startIfBlockForParamNotNil(parents []*properties.SpecParam, param *properties.SpecParam, parentParam *properties.SpecParam, builder *strings.Builder) {
grandparent := parents[0]

if grandparent != param && grandparent.Type == "list" && grandparent.Items.Type == "entry" {
if isParamName(param) {
builder.WriteString(fmt.Sprintf("if o%s != \"\" {\n",
renderNestedVariableName(parent, true, true, false)))
renderNestedVariableName(parents, true, true, false)))
} else {
builder.WriteString(fmt.Sprintf("if o%s != nil {\n",
renderNestedVariableName(parent, true, true, false)))
renderNestedVariableName(parents, true, true, false)))
}
} else {
if isParamName(param) {
builder.WriteString(fmt.Sprintf("if o%s != \"\" {\n",
renderNestedVariableName(parent, true, true, true)))
renderNestedVariableName(parents, true, true, true)))
} else {
builder.WriteString(fmt.Sprintf("if o%s != nil {\n",
renderNestedVariableName(parent, true, true, true)))
renderNestedVariableName(parents, true, true, true)))
}
}

}

func finishNestedObjectIfBlock(parent []*properties.SpecParam, param *properties.SpecParam, builder *strings.Builder) {
Expand Down Expand Up @@ -193,8 +196,8 @@ func declareRootOfNestedObject(parent []*properties.SpecParam, builder *strings.

func assignEmptyStructForNestedObject(parent []*properties.SpecParam, builder *strings.Builder, param *properties.SpecParam, objectType, version, prefix, suffix string) {
if isParamListAndProfileTypeIsExtendedEntry(param) {
createListAndLoopForNestedEntry(parent, builder, prefix, suffix, version)
miscForUnknownXmlWithExtendedEntry(parent, builder, suffix)
createListAndLoopForNestedEntry(parent, param, builder, prefix, suffix, version)
miscForUnknownXmlWithExtendedEntry(parent, objectType, builder, suffix)
} else {
createStructForParamWithSpec(parent, builder, prefix, suffix, version)
miscForUnknownXmlWithSpec(parent, builder, suffix, objectType)
Expand All @@ -209,7 +212,7 @@ func createStructForParamWithSpec(parent []*properties.SpecParam, builder *strin
CreateGoSuffixFromVersion(version)))
}

func createListAndLoopForNestedEntry(parent []*properties.SpecParam, builder *strings.Builder, prefix string, suffix string, version string) {
func createListAndLoopForNestedEntry(parent []*properties.SpecParam, param *properties.SpecParam, builder *strings.Builder, prefix string, suffix string, version string) {
if len(parent) == 1 && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
builder.WriteString(fmt.Sprintf("nested%sCol = []%s%s%s%s{}\n",
renderNestedVariableName(parent, true, true, false), prefix,
Expand All @@ -222,9 +225,14 @@ func createListAndLoopForNestedEntry(parent []*properties.SpecParam, builder *st
CreateGoSuffixFromVersion(version)))
}

builder.WriteString(fmt.Sprintf("for _, o%s := range o%s {\n",
startFromDot := true
if len(parent) == 2 && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
startFromDot = false
}

builder.WriteString(fmt.Sprintf("for _, o%s := range o%s { \n",
renderNestedVariableName(parent, false, false, false),
renderNestedVariableName(parent, true, true, true)))
renderNestedVariableName(parent, true, true, startFromDot)))
builder.WriteString(fmt.Sprintf("nested%s := %s%s%s%s{}\n",
renderNestedVariableName(parent, false, false, false),
prefix, renderNestedVariableName(parent, false, false, false), suffix,
Expand All @@ -240,16 +248,21 @@ func miscForUnknownXmlWithSpec(parent []*properties.SpecParam, builder *strings.
renderNestedVariableName(parent, false, false, false),
))
} else {
startsWithDot := true
if parent[0].Type == "list" && parent[0].Items.Type == "entry" {
startsWithDot = false
}

builder.WriteString(fmt.Sprintf("if o%s.Misc != nil {\n",
renderNestedVariableName(parent, true, true, true)))
renderNestedVariableName(parent, true, true, startsWithDot)))
builder.WriteString(fmt.Sprintf("%s.Misc[\"%s\"] = o%s.Misc\n",
objectType, renderNestedVariableName(parent, false, false, false),
renderNestedVariableName(parent, true, true, true),
renderNestedVariableName(parent, true, true, startsWithDot),
))
}
}

func miscForUnknownXmlWithExtendedEntry(parent []*properties.SpecParam, builder *strings.Builder, suffix string) {
func miscForUnknownXmlWithExtendedEntry(parent []*properties.SpecParam, objectType string, builder *strings.Builder, suffix string) {
if suffix == "Xml" {
builder.WriteString(fmt.Sprintf("if _, ok := o.Misc[\"%s\"]; ok {\n",
renderNestedVariableName(parent, false, false, false)))
Expand All @@ -260,7 +273,8 @@ func miscForUnknownXmlWithExtendedEntry(parent []*properties.SpecParam, builder
} else {
builder.WriteString(fmt.Sprintf("if o%s.Misc != nil {\n",
renderNestedVariableName(parent, false, false, false)))
builder.WriteString(fmt.Sprintf("entry.Misc[\"%s\"] = o%s.Misc\n",
builder.WriteString(fmt.Sprintf("%s.Misc[\"%s\"] = o%s.Misc\n",
objectType,
renderNestedVariableName(parent, false, false, false),
renderNestedVariableName(parent, false, false, false),
))
Expand All @@ -270,7 +284,7 @@ func miscForUnknownXmlWithExtendedEntry(parent []*properties.SpecParam, builder
var _ = log.Printf

func assignValueForNestedObject(parent []*properties.SpecParam, builder *strings.Builder, param, parentParam *properties.SpecParam) {
if len(parent) == 2 && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
if parent[0] != param && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
builder.WriteString(fmt.Sprintf("nested%s = o%s\n",
renderNestedVariableName(parent, true, true, false),
renderNestedVariableName(parent, true, true, false)))
Expand All @@ -282,8 +296,9 @@ func assignValueForNestedObject(parent []*properties.SpecParam, builder *strings
}

func assignFunctionForNestedObject(parent []*properties.SpecParam, functionName, additionalArguments string, builder *strings.Builder, param, parentParam *properties.SpecParam) {

var startWithDot bool
if len(parent) == 2 && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
if parent[0] != param && parent[0].Type == "list" && parent[0].Items.Type == "entry" {
startWithDot = false
} else {
startWithDot = true
Expand Down
2 changes: 2 additions & 0 deletions pkg/translate/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func specMatchFunctionName(parent []string, param *properties.SpecParam) string
return "util.IntsMatch"
} else if param.Type == "int64" {
return "util.Ints64Match"
} else if param.Type == "float64" {
return "util.FloatsMatch"
} else {
return fmt.Sprintf("match%s%s", strings.Join(parent, ""), param.Name.CamelCase)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/translate/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func checkNestedSpecs(parent []string, spec *properties.Spec, nestedSpecs map[st
}
for _, param := range spec.OneOf {
updateNestedSpecs(append(parent, param.Name.CamelCase), param, nestedSpecs)
if len(param.Profiles) > 0 && param.Profiles[0].Type == "entry" && param.Items != nil && param.Items.Type == "entry" {
addNameAsParamForNestedSpec(append(parent, param.Name.CamelCase), nestedSpecs)
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/translate/structs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestLocationType(t *testing.T) {
// given
yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec))

locationKeys := []string{"device_group", "shared"}
locationKeys := []string{"device-group", "shared"}
locations := yamlParsedData.Locations
var locationTypes []string

Expand Down Expand Up @@ -66,7 +66,7 @@ func TestOmitEmpty(t *testing.T) {

// given
yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec))
locationKeys := []string{"device_group", "shared"}
locationKeys := []string{"device-group", "shared"}
locations := yamlParsedData.Locations
var omitEmptyLocations []string

Expand Down
Loading
Loading