From 49457a5e760b853d73cf1f06a4821a9aef4796b9 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 2 Apr 2024 18:18:31 +0200 Subject: [PATCH] feat: Render `config.go` (first stage) and improvements in rendering `entry.go` (#35) --- pkg/generate/generator.go | 28 +- pkg/properties/normalized.go | 38 ++- pkg/properties/normalized_test.go | 18 +- pkg/translate/funcs.go | 224 +++++++++++----- pkg/translate/funcs_test.go | 114 ++++++-- pkg/translate/structs.go | 100 ++++++- pkg/translate/structs_test.go | 80 +++++- pkg/version/version.go | 72 +++++ pkg/version/version_test.go | 49 ++++ specs/device/dns.yaml | 21 +- specs/panorama/device-group.yaml | 3 +- specs/policies/security-policy-rule.yaml | 34 +++ templates/sdk/config.tmpl | 188 ++++++++++++++ templates/sdk/entry.tmpl | 317 +++++++++++++---------- templates/sdk/interfaces.tmpl | 10 +- templates/sdk/location.tmpl | 101 ++++---- 16 files changed, 1095 insertions(+), 302 deletions(-) create mode 100644 pkg/version/version.go create mode 100644 pkg/version/version_test.go create mode 100644 templates/sdk/config.tmpl diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index d6c35bb1..7b6d925a 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -119,21 +119,25 @@ func (c *Creator) createFile(filePath string) (*os.File, error) { func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := filepath.Join(c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, - "locationType": translate.LocationType, - "specParamType": translate.SpecParamType, - "xmlParamType": translate.XmlParamType, - "xmlTag": translate.XmlTag, - "specifyEntryAssignment": translate.SpecifyEntryAssignment, - "normalizeAssignment": translate.NormalizeAssignment, - "specMatchesFunction": translate.SpecMatchesFunction, - "omitEmpty": translate.OmitEmpty, - "contains": strings.Contains, + "packageName": translate.PackageName, + "locationType": translate.LocationType, + "specParamType": translate.SpecParamType, + "xmlParamType": translate.XmlParamType, + "xmlName": translate.XmlName, + "xmlTag": translate.XmlTag, + "specifyEntryAssignment": translate.SpecifyEntryAssignment, + "normalizeAssignment": translate.NormalizeAssignment, + "specMatchesFunction": translate.SpecMatchesFunction, + "nestedSpecMatchesFunction": translate.NestedSpecMatchesFunction, + "omitEmpty": translate.OmitEmpty, + "contains": strings.Contains, "subtract": func(a, b int) int { return a - b }, - "generateEntryXpath": translate.GenerateEntryXpathForLocation, - "nestedSpecs": translate.NestedSpecs, + "generateEntryXpath": translate.GenerateEntryXpathForLocation, + "nestedSpecs": translate.NestedSpecs, + "createGoSuffixFromVersion": translate.CreateGoSuffixFromVersion, + "paramSupportedInVersion": translate.ParamSupportedInVersion, } return template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) } diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index a24dd4e8..f6a0137c 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -227,7 +227,7 @@ func (spec *Normalization) AddNameVariantsForParams() error { // addDefaultTypesForParams recursively add default types for params for nested specs. func addDefaultTypesForParams(params map[string]*SpecParam) error { for _, param := range params { - if param.Type == "" { + if param.Type == "" && param.Spec == nil { param.Type = "string" } @@ -295,3 +295,39 @@ func (spec *Normalization) Validate() []error { return checks } + +// SupportedVersions provides list of all supported versions in format MAJOR.MINOR.PATCH +func (spec *Normalization) SupportedVersions() []string { + if spec.Spec != nil { + versions := supportedVersions(spec.Spec.Params, []string{""}) + versions = supportedVersions(spec.Spec.OneOf, versions) + return versions + } + return nil +} + +func supportedVersions(params map[string]*SpecParam, versions []string) []string { + for _, param := range params { + for _, profile := range param.Profiles { + if profile.FromVersion != "" { + if notExist := listContains(versions, profile.FromVersion); notExist { + versions = append(versions, profile.FromVersion) + } + } + } + if param.Spec != nil { + versions = supportedVersions(param.Spec.Params, versions) + versions = supportedVersions(param.Spec.OneOf, versions) + } + } + return versions +} + +func listContains(versions []string, checkedVersion string) bool { + for _, version := range versions { + if version == checkedVersion { + return false + } + } + return true +} diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index e722344a..bd0aeab8 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -85,6 +85,7 @@ spec: profiles: - xpath: ["description"] + from_version: "10.1.1" tags: description: 'The administrative tags.' type: 'list' @@ -123,6 +124,7 @@ spec: profiles: - xpath: ["ip-wildcard"] + from_version: "11.1.2" ` func TestUnmarshallAddressSpecFile(t *testing.T) { @@ -266,7 +268,7 @@ spec: - xpath: - description not_present: false - from_version: "" + from_version: 10.1.1 spec: null tags: name: @@ -346,7 +348,7 @@ spec: - xpath: - ip-wildcard not_present: false - from_version: "" + from_version: 11.1.2 spec: null ` @@ -412,3 +414,15 @@ xpath_suffix: // then assert.Len(t, problems, 2, "Not all expected validation checks failed") } + +func TestGettingListOfSupportedVersions(t *testing.T) { + // given + yamlParsedData, _ := ParseSpec([]byte(sampleSpec)) + + // when + versions := yamlParsedData.SupportedVersions() + + // then + assert.NotNilf(t, yamlParsedData, "Unmarshalled data cannot be nil") + assert.Contains(t, versions, "10.1.1") +} diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 251e4a14..902434bc 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -24,31 +24,35 @@ func generateEntryXpathForLocation(location string, xpath string) string { return asEntryXpath } -// NormalizeAssignment generates a string, which contains entry assignment in Normalize() function -// in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, -// which are declaring additional variables (function nestedObjectDeclaration()) and use them in -// entry assignment (function nestedObjectAssignment()). -func NormalizeAssignment(param *properties.SpecParam) string { - return prepareAssignment(param, "util.MemToStr", "") +// NormalizeAssignment generates a string, which contains entry/config assignment in Normalize() function +// in entry.tmpl/config.tmpl template. If param contains nested specs, then recursively are executed +// internal functions, which are creating entry assignment. +func NormalizeAssignment(objectType string, param *properties.SpecParam, version string) string { + return prepareAssignment(objectType, param, "util.MemToStr", "Spec", "", version) } -// SpecifyEntryAssignment generates a string, which contains entry assignment in SpecifyEntry() function -// in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, -// which are declaring additional variables (function nestedObjectDeclaration()) and use them in -// entry assignment (function nestedObjectAssignment()). -func SpecifyEntryAssignment(param *properties.SpecParam) string { - return prepareAssignment(param, "util.StrToMem", "Xml") +// SpecifyEntryAssignment generates a string, which contains entry/config assignment in SpecifyEntry() function +// in entry.tmpl/config.tmpl template. If param contains nested specs, then recursively are executed +// internal functions, which are creating entry assignment. +func SpecifyEntryAssignment(objectType string, param *properties.SpecParam, version string) string { + return prepareAssignment(objectType, param, "util.StrToMem", "spec", "Xml", version) } -func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix string) string { +func prepareAssignment(objectType string, param *properties.SpecParam, listFunction, specPrefix, specSuffix string, version string) string { var builder strings.Builder - if param.Spec != nil { - appendSpecObjectAssignment(param, specSuffix, &builder) - } else if isParamListAndProfileTypeIsMember(param) { - appendListFunctionAssignment(param, listFunction, &builder) - } else { - appendSimpleAssignment(param, &builder) + if ParamSupportedInVersion(param, version) { + if param.Spec != nil { + if specSuffix == "Xml" { + appendSpecObjectAssignment(param, objectType, version, specPrefix, specSuffix, &builder) + } else { + appendSpecObjectAssignment(param, objectType, "", specPrefix, specSuffix, &builder) + } + } else if isParamListAndProfileTypeIsMember(param) { + appendListFunctionAssignment(param, objectType, listFunction, &builder) + } else { + appendSimpleAssignment(param, objectType, &builder) + } } return builder.String() @@ -58,70 +62,164 @@ func isParamListAndProfileTypeIsMember(param *properties.SpecParam) bool { return param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" } -func appendSimpleAssignment(param *properties.SpecParam, builder *strings.Builder) { - builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) +func appendSimpleAssignment(param *properties.SpecParam, objectType string, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("%s.%s = o.%s", objectType, param.Name.CamelCase, param.Name.CamelCase)) } -func appendListFunctionAssignment(param *properties.SpecParam, listFunction string, builder *strings.Builder) { - builder.WriteString(fmt.Sprintf("entry.%s = %s(o.%s)", param.Name.CamelCase, listFunction, param.Name.CamelCase)) +func appendListFunctionAssignment(param *properties.SpecParam, objectType string, listFunction string, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("%s.%s = %s(o.%s)", objectType, param.Name.CamelCase, listFunction, param.Name.CamelCase)) } -func appendSpecObjectAssignment(param *properties.SpecParam, suffix string, builder *strings.Builder) { - appendNestedObjectDeclaration([]string{param.Name.CamelCase}, param.Spec.Params, builder) - appendNestedObjectDeclaration([]string{param.Name.CamelCase}, param.Spec.OneOf, builder) - - builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s%s{\n", param.Name.CamelCase, param.Name.CamelCase, suffix)) - - appendNestedObjectAssignment([]string{param.Name.CamelCase}, param.Spec.Params, suffix, builder) - appendNestedObjectAssignment([]string{param.Name.CamelCase}, param.Spec.OneOf, suffix, builder) - - builder.WriteString("}\n") +func appendSpecObjectAssignment(param *properties.SpecParam, objectType string, version, prefix, suffix string, builder *strings.Builder) { + defineNestedObject([]string{param.Name.CamelCase}, param, objectType, version, prefix, suffix, builder) + builder.WriteString(fmt.Sprintf("%s.%s = nested%s\n", objectType, param.Name.CamelCase, param.Name.CamelCase)) } -func appendNestedObjectDeclaration(parent []string, params map[string]*properties.SpecParam, builder *strings.Builder) { - for _, subParam := range params { - appendDeclarationForNestedObject(parent, subParam, builder) +func defineNestedObject(parent []string, param *properties.SpecParam, objectType string, version, prefix, suffix string, builder *strings.Builder) { + declareRootOfNestedObject(parent, builder, version, prefix, suffix) + + if ParamSupportedInVersion(param, version) { + builder.WriteString(fmt.Sprintf("if o.%s != nil {\n", strings.Join(parent, "."))) + if param.Spec != nil { + assignEmptyStructForNestedObject(parent, builder, objectType, version, prefix, suffix) + defineNestedObjectForChildParams(parent, param.Spec.Params, objectType, version, prefix, suffix, builder) + defineNestedObjectForChildParams(parent, param.Spec.OneOf, objectType, version, prefix, suffix, builder) + } else { + assignValueForNestedObject(parent, builder) + } + builder.WriteString("}\n") } } -func appendDeclarationForNestedObject(parent []string, param *properties.SpecParam, builder *strings.Builder) { - if param.Spec != nil { - appendNestedObjectDeclaration(append(parent, param.Name.CamelCase), param.Spec.Params, builder) - appendNestedObjectDeclaration(append(parent, param.Name.CamelCase), param.Spec.OneOf, builder) - } else { - builder.WriteString(fmt.Sprintf("nested%s%s := o.%s.%s\n", - strings.Join(parent, ""), param.Name.CamelCase, - strings.Join(parent, "."), param.Name.CamelCase)) +func declareRootOfNestedObject(parent []string, builder *strings.Builder, version, prefix, suffix string) { + if len(parent) == 1 { + builder.WriteString(fmt.Sprintf("nested%s := &%s%s%s%s{}\n", + strings.Join(parent, "."), prefix, + strings.Join(parent, ""), suffix, + CreateGoSuffixFromVersion(version))) } } -func appendNestedObjectAssignment(parent []string, params map[string]*properties.SpecParam, suffix string, builder *strings.Builder) { - for _, subParam := range params { - appendAssignmentForNestedObject(parent, subParam, suffix, builder) +func assignEmptyStructForNestedObject(parent []string, builder *strings.Builder, objectType, version, prefix, suffix string) { + if len(parent) > 1 { + builder.WriteString(fmt.Sprintf("nested%s = &%s%s%s%s{}\n", + strings.Join(parent, "."), prefix, strings.Join(parent, ""), suffix, + CreateGoSuffixFromVersion(version))) + + if suffix == "Xml" { + builder.WriteString(fmt.Sprintf("if _, ok := o.Misc[\"%s\"]; ok {\n", + strings.Join(parent, ""))) + builder.WriteString(fmt.Sprintf("nested%s.Misc = o.Misc[\"%s\"]\n", + strings.Join(parent, "."), strings.Join(parent, ""), + )) + } else { + builder.WriteString(fmt.Sprintf("if o.%s.Misc != nil {\n", + strings.Join(parent, "."))) + builder.WriteString(fmt.Sprintf("%s.Misc[\"%s\"] = o.%s.Misc\n", + objectType, strings.Join(parent, ""), strings.Join(parent, "."), + )) + } + builder.WriteString("}\n") } } -func appendAssignmentForNestedObject(parent []string, param *properties.SpecParam, suffix string, builder *strings.Builder) { - if param.Spec != nil { - builder.WriteString(fmt.Sprintf("%s : &Spec%s%s%s{\n", param.Name.CamelCase, - strings.Join(parent, ""), param.Name.CamelCase, suffix)) - appendNestedObjectAssignment(append(parent, param.Name.CamelCase), param.Spec.Params, suffix, builder) - appendNestedObjectAssignment(append(parent, param.Name.CamelCase), param.Spec.OneOf, suffix, builder) - builder.WriteString("},\n") - } else if isParamListAndProfileTypeIsMember(param) { - builder.WriteString(fmt.Sprintf("%s : util.StrToMem(o.%s),\n", - param.Name.CamelCase, param.Name.CamelCase)) - } else { - builder.WriteString(fmt.Sprintf("%s : nested%s%s,\n", - param.Name.CamelCase, strings.Join(parent, ""), param.Name.CamelCase)) +func assignValueForNestedObject(parent []string, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("nested%s = o.%s\n", + strings.Join(parent, "."), + strings.Join(parent, "."))) +} + +func defineNestedObjectForChildParams(parent []string, params map[string]*properties.SpecParam, objectType string, version, prefix, suffix string, builder *strings.Builder) { + for _, param := range params { + defineNestedObject(append(parent, param.Name.CamelCase), param, objectType, version, prefix, suffix, builder) } } -// SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl +// SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl/config.tmpl // to compare all items of generated entry. func SpecMatchesFunction(param *properties.SpecParam) string { + return specMatchFunctionName([]string{}, param) +} + +func specMatchFunctionName(parent []string, param *properties.SpecParam) string { if param.Type == "list" { - return "OrderedListsMatch" + return "util.OrderedListsMatch" + } else if param.Type == "string" { + return "util.OptionalStringsMatch" + } else { + return fmt.Sprintf("specMatch%s%s", strings.Join(parent, ""), param.Name.CamelCase) + } +} + +// NestedSpecMatchesFunction return a string with body of specMach* functions required for nested params +func NestedSpecMatchesFunction(spec *properties.Spec) string { + var builder strings.Builder + + defineSpecMatchesFunction([]string{}, spec.Params, &builder) + defineSpecMatchesFunction([]string{}, spec.OneOf, &builder) + + return builder.String() +} + +func defineSpecMatchesFunction(parent []string, params map[string]*properties.SpecParam, builder *strings.Builder) { + for _, param := range params { + if param.Spec != nil { + defineSpecMatchesFunction(append(parent, param.Name.CamelCase), param.Spec.Params, builder) + defineSpecMatchesFunction(append(parent, param.Name.CamelCase), param.Spec.OneOf, builder) + + renderSpecMatchesFunctionNameWithArguments(parent, builder, param) + checkInSpecMatchesFunctionIfVariablesAreNil(builder) + + for _, subParam := range param.Spec.Params { + renderInSpecMatchesFunctionIfToCheckIfVariablesMatches(parent, builder, param, subParam) + } + for _, subParam := range param.Spec.OneOf { + renderInSpecMatchesFunctionIfToCheckIfVariablesMatches(parent, builder, param, subParam) + } + + builder.WriteString("return true\n") + builder.WriteString("}\n") + } else if param.Type != "list" && param.Type != "string" { + // whole section should be removed, when there will be dedicated function to compare integers + // in file https://github.com/PaloAltoNetworks/pango/blob/develop/util/comparison.go + renderSpecMatchesFunctionNameWithArguments(parent, builder, param) + checkInSpecMatchesFunctionIfVariablesAreNil(builder) + + builder.WriteString("return *a == *b\n") + builder.WriteString("}\n") + } + } +} + +func renderSpecMatchesFunctionNameWithArguments(parent []string, builder *strings.Builder, param *properties.SpecParam) { + builder.WriteString(fmt.Sprintf("func specMatch%s%s(a *%s, b *%s) bool {", + strings.Join(parent, ""), param.Name.CamelCase, + argumentTypeForSpecMatchesFunction(parent, param), + argumentTypeForSpecMatchesFunction(parent, param))) +} + +func checkInSpecMatchesFunctionIfVariablesAreNil(builder *strings.Builder) { + builder.WriteString("if a == nil && b != nil || a != nil && b == nil {\n") + builder.WriteString(" return false\n") + builder.WriteString("} else if a == nil && b == nil {\n") + builder.WriteString(" return true\n") + builder.WriteString("}\n") +} + +func renderInSpecMatchesFunctionIfToCheckIfVariablesMatches(parent []string, builder *strings.Builder, param *properties.SpecParam, subparam *properties.SpecParam) { + builder.WriteString(fmt.Sprintf("if !%s(a.%s, b.%s) {\n", + specMatchFunctionName(append(parent, param.Name.CamelCase), subparam), subparam.Name.CamelCase, subparam.Name.CamelCase)) + builder.WriteString(" return false\n") + builder.WriteString("}\n") +} + +func argumentTypeForSpecMatchesFunction(parent []string, param *properties.SpecParam) string { + if param.Type == "bool" { + return "bool" + } else if param.Type == "int" { + return "int" + } else { + return fmt.Sprintf("Spec%s%s", + strings.Join(parent, ""), param.Name.CamelCase) } - return "OptionalStringsMatch" } diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index 0c048edf..a9eb00b6 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -45,8 +45,8 @@ func TestSpecifyEntryAssignmentForFlatStructure(t *testing.T) { } // when - calculatedAssignmentString := SpecifyEntryAssignment(¶mTypeString) - calculatedAssignmentListString := SpecifyEntryAssignment(¶mTypeListString) + calculatedAssignmentString := SpecifyEntryAssignment("entry", ¶mTypeString, "") + calculatedAssignmentListString := SpecifyEntryAssignment("entry", ¶mTypeListString, "") // then assert.Equal(t, "entry.Description = o.Description", calculatedAssignmentString) @@ -85,15 +85,22 @@ func TestSpecifyEntryAssignmentForNestedObject(t *testing.T) { }, }, } - expectedAssignmentStreing := `nestedABC := o.A.B.C -entry.A = &SpecAXml{ -B : &SpecABXml{ -C : nestedABC, -}, + expectedAssignmentStreing := `nestedA := &specAXml{} +if o.A != nil { +if o.A.B != nil { +nestedA.B = &specABXml{} +if _, ok := o.Misc["AB"]; ok { +nestedA.B.Misc = o.Misc["AB"] } +if o.A.B.C != nil { +nestedA.B.C = o.A.B.C +} +} +} +entry.A = nestedA ` // when - calculatedAssignmentString := SpecifyEntryAssignment(spec.Params["a"]) + calculatedAssignmentString := SpecifyEntryAssignment("entry", spec.Params["a"], "") // then assert.Equal(t, expectedAssignmentStreing, calculatedAssignmentString) @@ -131,15 +138,22 @@ func TestNormalizeAssignmentForNestedObject(t *testing.T) { }, }, } - expectedAssignmentStreing := `nestedABC := o.A.B.C -entry.A = &SpecA{ -B : &SpecAB{ -C : nestedABC, -}, + expectedAssignmentStreing := `nestedA := &SpecA{} +if o.A != nil { +if o.A.B != nil { +nestedA.B = &SpecAB{} +if o.A.B.Misc != nil { +entry.Misc["AB"] = o.A.B.Misc +} +if o.A.B.C != nil { +nestedA.B.C = o.A.B.C } +} +} +entry.A = nestedA ` // when - calculatedAssignmentString := NormalizeAssignment(spec.Params["a"]) + calculatedAssignmentString := NormalizeAssignment("entry", spec.Params["a"], "") // then assert.Equal(t, expectedAssignmentStreing, calculatedAssignmentString) @@ -159,6 +173,74 @@ func TestSpecMatchesFunction(t *testing.T) { calculatedSpecMatchFunctionListString := SpecMatchesFunction(¶mTypeListString) // then - assert.Equal(t, "OptionalStringsMatch", calculatedSpecMatchFunctionString) - assert.Equal(t, "OrderedListsMatch", calculatedSpecMatchFunctionListString) + assert.Equal(t, "util.OptionalStringsMatch", calculatedSpecMatchFunctionString) + assert.Equal(t, "util.OrderedListsMatch", calculatedSpecMatchFunctionListString) +} + +func TestNestedSpecMatchesFunction(t *testing.T) { + // given + spec := properties.Spec{ + Params: map[string]*properties.SpecParam{ + "a": { + Name: &properties.NameVariant{ + Underscore: "a", + CamelCase: "A", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "b": { + Name: &properties.NameVariant{ + Underscore: "b", + CamelCase: "B", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "c": { + Name: &properties.NameVariant{ + Underscore: "c", + CamelCase: "C", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + expectedNestedSpec := `func specMatchABC(a *SpecABC, b *SpecABC) bool {if a == nil && b != nil || a != nil && b == nil { + return false +} else if a == nil && b == nil { + return true +} +return *a == *b +} +func specMatchAB(a *SpecAB, b *SpecAB) bool {if a == nil && b != nil || a != nil && b == nil { + return false +} else if a == nil && b == nil { + return true +} +if !specMatchABC(a.C, b.C) { + return false +} +return true +} +func specMatchA(a *SpecA, b *SpecA) bool {if a == nil && b != nil || a != nil && b == nil { + return false +} else if a == nil && b == nil { + return true +} +if !specMatchAB(a.B, b.B) { + return false +} +return true +} +` + + // when + renderedNestedSpecMatches := NestedSpecMatchesFunction(&spec) + + // then + assert.Equal(t, expectedNestedSpec, renderedNestedSpecMatches) } diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index d96638e6..b3639ce1 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "github.com/paloaltonetworks/pan-os-codegen/pkg/version" "strings" ) @@ -47,7 +48,7 @@ func updateNestedSpecs(parent []string, param *properties.SpecParam, nestedSpecs // SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func SpecParamType(parent string, param *properties.SpecParam) string { - prefix := determinePrefix(param) + prefix := determinePrefix(param, false) calculatedType := "" if param.Type == "list" && param.Items != nil { @@ -63,7 +64,7 @@ func SpecParamType(parent string, param *properties.SpecParam) string { // XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func XmlParamType(parent string, param *properties.SpecParam) string { - prefix := determinePrefix(param) + prefix := determinePrefix(param, true) calculatedType := "" if isParamListAndProfileTypeIsMember(param) { @@ -77,15 +78,13 @@ func XmlParamType(parent string, param *properties.SpecParam) string { return fmt.Sprintf("%s%s", prefix, calculatedType) } -func determinePrefix(param *properties.SpecParam) string { - prefix := "" - if param.Type == "list" { - prefix = prefix + "[]" - } - if !param.Required { - prefix = prefix + "*" +func determinePrefix(param *properties.SpecParam, useMemberTypeStruct bool) string { + if param.Type == "list" && !(useMemberTypeStruct && isParamListAndProfileTypeIsMember(param)) { + return "[]" + } else if !param.Required { + return "*" } - return prefix + return "" } func determineListType(param *properties.SpecParam) string { @@ -100,7 +99,16 @@ func calculateNestedSpecType(parent string, param *properties.SpecParam) string } func calculateNestedXmlSpecType(parent string, param *properties.SpecParam) string { - return fmt.Sprintf("Spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) + return fmt.Sprintf("spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) +} + +// XmlName creates a string with xml name (e.g. `description`). +func XmlName(param *properties.SpecParam) string { + if param.Profiles != nil && len(param.Profiles) > 0 { + return param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1] + } + + return "" } // XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`). @@ -111,7 +119,7 @@ func XmlTag(param *properties.SpecParam) string { suffix = ",omitempty" } - return fmt.Sprintf("`xml:\"%s%s\"`", param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1], suffix) + return fmt.Sprintf("`xml:\"%s%s\"`", XmlName(param), suffix) } return "" @@ -125,3 +133,71 @@ func OmitEmpty(location *properties.Location) string { return "" } } + +// CreateGoSuffixFromVersion convert version into Go suffix e.g. 10.1.1 into _10_1_1 +func CreateGoSuffixFromVersion(version string) string { + if len(version) > 0 { + return fmt.Sprintf("_%s", strings.ReplaceAll(version, ".", "_")) + } else { + return version + } +} + +// ParamSupportedInVersion checks if param is supported in specific PAN-OS version +func ParamSupportedInVersion(param *properties.SpecParam, deviceVersionStr string) bool { + var supported []bool + if deviceVersionStr == "" { + supported = listOfProfileSupportForNotDefinedDeviceVersion(param, supported) + } else { + deviceVersion, err := version.New(deviceVersionStr) + if err != nil { + return false + } + + supported, err = listOfProfileSupportForDefinedDeviceVersion(param, supported, deviceVersion) + if err != nil { + return false + } + } + return allTrue(supported) +} + +func listOfProfileSupportForNotDefinedDeviceVersion(param *properties.SpecParam, supported []bool) []bool { + for _, profile := range param.Profiles { + if profile.FromVersion != "" { + supported = append(supported, profile.NotPresent) + } else { + supported = append(supported, true) + } + } + return supported +} + +func listOfProfileSupportForDefinedDeviceVersion(param *properties.SpecParam, supported []bool, deviceVersion version.Version) ([]bool, error) { + for _, profile := range param.Profiles { + if profile.FromVersion != "" { + paramProfileVersion, err := version.New(profile.FromVersion) + if err != nil { + return nil, err + } + + if deviceVersion.Gte(paramProfileVersion) { + supported = append(supported, !profile.NotPresent) + } else { + supported = append(supported, profile.NotPresent) + } + } else { + supported = append(supported, !profile.NotPresent) + } + } + return supported, nil +} + +func allTrue(values []bool) bool { + for _, value := range values { + if !value { + return false + } + } + return true +} diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 4a3cb1bf..14a43c08 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -91,7 +91,7 @@ func TestSpecParamType(t *testing.T) { // then assert.Equal(t, "string", calculatedTypeRequiredString) - assert.Equal(t, "[]*string", calculatedTypeListString) + assert.Equal(t, "[]string", calculatedTypeListString) assert.Equal(t, "*string", calculatedTypeOptionalString) } @@ -144,7 +144,7 @@ func TestXmlParamType(t *testing.T) { // then assert.Equal(t, "string", calculatedTypeRequiredString) - assert.Equal(t, "[]*util.MemberType", calculatedTypeListString) + assert.Equal(t, "*util.MemberType", calculatedTypeListString) } func TestXmlTag(t *testing.T) { @@ -222,3 +222,79 @@ func TestNestedSpecs(t *testing.T) { assert.Contains(t, nestedSpecs, "A") assert.Contains(t, nestedSpecs, "AB") } + +func TestCreateGoSuffixFromVersion(t *testing.T) { + // given + + // when + suffix := CreateGoSuffixFromVersion("10.1.1") + + // then + assert.Equal(t, "_10_1_1", suffix) +} + +func TestParamSupportedInVersion(t *testing.T) { + // given + deviceVersion101 := "10.1.1" + deviceVersion90 := "9.0.0" + + paramName := properties.NameVariant{ + CamelCase: "test", + Underscore: "test", + } + + profileAlwaysPresent := properties.SpecParamProfile{ + Xpath: []string{"test"}, + } + profilePresentFrom10 := properties.SpecParamProfile{ + Xpath: []string{"test"}, + FromVersion: "10.0.0", + } + profileNotPresentFrom10 := properties.SpecParamProfile{ + Xpath: []string{"test"}, + FromVersion: "10.0.0", + NotPresent: true, + } + + paramPresentFrom10 := &properties.SpecParam{ + Type: "string", + Name: ¶mName, + Profiles: []*properties.SpecParamProfile{ + &profilePresentFrom10, + }, + } + paramAlwaysPresent := &properties.SpecParam{ + Type: "string", + Name: ¶mName, + Profiles: []*properties.SpecParamProfile{ + &profileAlwaysPresent, + }, + } + paramNotPresentFrom10 := &properties.SpecParam{ + Type: "string", + Name: ¶mName, + Profiles: []*properties.SpecParamProfile{ + &profileNotPresentFrom10, + }, + } + + // when + noVersionAndParamAlwaysPresent := ParamSupportedInVersion(paramAlwaysPresent, "") + noVersionAndParamNotPresentFrom10 := ParamSupportedInVersion(paramNotPresentFrom10, "") + device10AndParamPresentFrom10 := ParamSupportedInVersion(paramPresentFrom10, deviceVersion101) + device10AndParamAlwaysPresent := ParamSupportedInVersion(paramAlwaysPresent, deviceVersion101) + device10AndParamNotPresentFrom10 := ParamSupportedInVersion(paramNotPresentFrom10, deviceVersion101) + device9AndParamPresentFrom10 := ParamSupportedInVersion(paramPresentFrom10, deviceVersion90) + device9AndParamAlwaysPresent := ParamSupportedInVersion(paramAlwaysPresent, deviceVersion90) + device9AndParamNotPresentFrom10 := ParamSupportedInVersion(paramNotPresentFrom10, deviceVersion90) + + // then + assert.True(t, noVersionAndParamAlwaysPresent) + assert.True(t, noVersionAndParamNotPresentFrom10) + assert.True(t, device10AndParamPresentFrom10) + assert.True(t, device10AndParamAlwaysPresent) + assert.False(t, device10AndParamNotPresentFrom10) + assert.False(t, device9AndParamPresentFrom10) + assert.True(t, device9AndParamAlwaysPresent) + assert.True(t, device9AndParamNotPresentFrom10) +} diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 00000000..df8175eb --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,72 @@ +package version + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// Version is the version number struct. +type Version struct { + Major, Minor, Patch int + Hotfix string +} + +// Gte tests if this version number is greater than or equal to the argument. +func (v Version) Gte(o Version) bool { + if v.Major != o.Major { + return v.Major > o.Major + } + + if v.Minor != o.Minor { + return v.Minor > o.Minor + } + + return v.Patch >= o.Patch +} + +// String returns the version number as a string. +func (v Version) String() string { + if v.Hotfix == "" { + return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) + } else { + return fmt.Sprintf("%d.%d.%d-%s", v.Major, v.Minor, v.Patch, v.Hotfix) + } +} + +// New returns a version number from the given string. +func New(version string) (Version, error) { + parts := strings.Split(version, ".") + if len(parts) != 3 { + return Version{}, errors.New("invalid version") + } + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return Version{}, fmt.Errorf("major %s is not a number: %s", parts[0], err) + } + + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return Version{}, fmt.Errorf("minor %s is not a number: %s", parts[0], err) + } + + patchWithHotfix := strings.Split(parts[2], "-") + + var hotfix string + if len(patchWithHotfix) == 1 { + hotfix = "" + } else if len(patchWithHotfix) == 2 { + hotfix = patchWithHotfix[1] + } else { + return Version{}, fmt.Errorf("patch %s is not formatted as expected", parts[2]) + } + + patch, err := strconv.Atoi(patchWithHotfix[0]) + if err != nil { + return Version{}, fmt.Errorf("patch %s is not a number: %s", parts[2], err) + } + + return Version{major, minor, patch, hotfix}, nil +} diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go new file mode 100644 index 00000000..b0a0f2f4 --- /dev/null +++ b/pkg/version/version_test.go @@ -0,0 +1,49 @@ +package version + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCorrectVersion(t *testing.T) { + // given + + // when + version1011, err1011 := New("10.1.1") + version1011h2, err1011h2 := New("10.1.1-h2") + + // then + assert.NoError(t, err1011) + assert.Equal(t, 10, version1011.Major) + assert.Equal(t, 1, version1011.Minor) + assert.Equal(t, 1, version1011.Patch) + assert.Equal(t, "", version1011.Hotfix) + assert.NoError(t, err1011h2) + assert.Equal(t, 10, version1011h2.Major) + assert.Equal(t, 1, version1011h2.Minor) + assert.Equal(t, 1, version1011h2.Patch) + assert.Equal(t, "h2", version1011h2.Hotfix) +} + +func TestIncorrectVersion(t *testing.T) { + // given + + // when + _, err101 := New("10.1") + _, err1011h2h2 := New("10.1.1-h2-h2") + + // then + assert.Error(t, err101) + assert.Error(t, err1011h2h2) +} + +func TestVersionComparison(t *testing.T) { + // given + + // when + v1, _ := New("10.1.1") + v2, _ := New("10.2.1-h2") + + // then + assert.True(t, v2.Gte(v1)) +} diff --git a/specs/device/dns.yaml b/specs/device/dns.yaml index 1991704f..20960ae3 100644 --- a/specs/device/dns.yaml +++ b/specs/device/dns.yaml @@ -71,15 +71,12 @@ spec: - xpath: ["servers"] spec: - properties: - params: - primary: - description: 'Primary DNS server IP address' - profiles: - - - xpath: ["primary"] - secondary: - description: 'Secondary DNS server IP address' - profiles: - - - xpath: ["secondary"] + params: + primary: + description: 'Primary DNS server IP address' + profiles: + - xpath: [ "primary" ] + secondary: + description: 'Secondary DNS server IP address' + profiles: + - xpath: [ "secondary" ] diff --git a/specs/panorama/device-group.yaml b/specs/panorama/device-group.yaml index 9fd5b545..4852e709 100644 --- a/specs/panorama/device-group.yaml +++ b/specs/panorama/device-group.yaml @@ -61,8 +61,7 @@ spec: items: type: 'string' profiles: - - - type: 'entry' + - type: 'member' xpath: ["devices"] authorization_code: description: 'Authorization code' diff --git a/specs/policies/security-policy-rule.yaml b/specs/policies/security-policy-rule.yaml index 978fa99d..8c926626 100644 --- a/specs/policies/security-policy-rule.yaml +++ b/specs/policies/security-policy-rule.yaml @@ -124,6 +124,8 @@ spec: type: list count: max: 31 + items: + type: 'string' profiles: - type: 'member' @@ -133,6 +135,8 @@ spec: type: list count: max: 31 + items: + type: 'string' profiles: - type: 'member' @@ -142,6 +146,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -151,6 +157,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -160,6 +168,8 @@ spec: type: list count: max: 1023 + items: + type: 'string' profiles: - type: 'member' @@ -169,6 +179,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -178,6 +190,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -187,6 +201,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -196,6 +212,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -205,6 +223,8 @@ spec: type: list count: max: 63 + items: + type: 'string' profiles: - type: 'member' @@ -287,6 +307,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -296,6 +318,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -305,6 +329,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -314,6 +340,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -323,6 +351,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -332,6 +362,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' @@ -341,6 +373,8 @@ spec: type: list count: max: 1 + items: + type: 'string' profiles: - type: 'member' diff --git a/templates/sdk/config.tmpl b/templates/sdk/config.tmpl new file mode 100644 index 00000000..e959a9a2 --- /dev/null +++ b/templates/sdk/config.tmpl @@ -0,0 +1,188 @@ +{{- if not .Entry}} + package {{packageName .GoSdkPath}} + + import ( + "encoding/xml" + "github.com/PaloAltoNetworks/pango/generic" + "github.com/PaloAltoNetworks/pango/version" + ) + + type Config{{createGoSuffixFromVersion ""}} struct { + {{- range $_, $param := $.Spec.Params}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} + {{- end}} + + Misc map[string][]generic.Xml + } + + {{- range $name, $spec := nestedSpecs $.Spec }} + type Spec{{$name}}{{createGoSuffixFromVersion ""}} struct { + {{- range $_, $param := $spec.Params}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} + {{- end}} + } + {{- end}} + + {{- range $version := .SupportedVersions }} + type configXmlContainer{{createGoSuffixFromVersion $version}} struct { + XMLName xml.Name `xml:"result"` + Answer []configXml{{createGoSuffixFromVersion $version}} `xml:"system"` + } + {{- end}} + + {{- range $version := .SupportedVersions }} + type configXml{{createGoSuffixFromVersion $version}} struct { + {{- range $_, $param := $.Spec.Params}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + + Misc []generic.Xml `xml:",any"` + } + {{- end}} + + {{- range $version := .SupportedVersions }} + {{- range $name, $spec := nestedSpecs $.Spec }} + type spec{{$name}}Xml{{createGoSuffixFromVersion $version}} struct { + {{- range $_, $param := $spec.Params}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + + Misc []generic.Xml `xml:",any"` + } + {{- end}} + {{- end}} + + func Versioning(vn version.Number) (Specifier, Normalizer, error) { + {{- $numberOfVersions := len .SupportedVersions }} + {{- if gt $numberOfVersions 1}} + {{- range $index, $version := .SupportedVersions }} + {{- if ne $version ""}} + version{{createGoSuffixFromVersion $version}}, err := version.New("{{$version}}") + if err != nil { + return nil, nil, err + } + {{- end}} + {{- end}} + {{- range $index, $version := .SupportedVersions }} + {{- if ne $version ""}} + {{- if eq $index 1}} + if vn.Gte(version{{createGoSuffixFromVersion $version}}) { + return specifyConfig{{createGoSuffixFromVersion $version}}, &configXmlContainer{{createGoSuffixFromVersion $version}}{}, nil + {{- else}} + } else if vn.Gte(version{{createGoSuffixFromVersion $version}}) { + return specifyConfig{{createGoSuffixFromVersion $version}}, &configXmlContainer{{createGoSuffixFromVersion $version}}{}, nil + {{- end}} + {{- end}} + {{- end}} + } else { + {{- end}} + return specifyConfig, &configXmlContainer{}, nil + {{- if gt $numberOfVersions 1}} + } + {{- end}} + } + + {{- range $version := .SupportedVersions }} + func specifyConfig{{createGoSuffixFromVersion $version}}(o Config) (any, error) { + config := configXml{{createGoSuffixFromVersion $version}}{} + + {{- range $_, $param := $.Spec.Params}} + {{specifyEntryAssignment "config" $param $version}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{specifyEntryAssignment "config" $param $version}} + {{- end}} + + config.Misc = o.Misc["Config"] + + return config, nil + } + {{- end}} + + {{- range $version := .SupportedVersions }} + func (c *configXmlContainer{{createGoSuffixFromVersion $version}}) Normalize() ([]Config, error) { + configList := make([]Config, 0, len(c.Answer)) + for _, o := range c.Answer { + config := Config{ + Misc: make(map[string][]generic.Xml), + } + {{- range $_, $param := $.Spec.Params}} + {{normalizeAssignment "config" $param $version}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{normalizeAssignment "config" $param $version}} + {{- end}} + + config.Misc["Config"] = o.Misc + + configList = append(configList, config) + } + + return configList, nil + } + {{- end}} + + // MarshalXML customized implementation of XML marshal due to requirement to skip 'system' as root for configuration settings + func (c configXml) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + {{- range $_, $param := $.Spec.Params}} + if c.{{$param.Name.CamelCase}} != nil { + start.Name = xml.Name{Local: "{{xmlName $param}}"} + if err := e.EncodeElement(c.{{$param.Name.CamelCase}}, start); err != nil { + return err + } + } + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + if c.{{$param.Name.CamelCase}} != nil { + start.Name = xml.Name{Local: "{{xmlName $param}}"} + if err := e.EncodeElement(c.{{$param.Name.CamelCase}}, start); err != nil { + return err + } + } + {{- end}} + + for _, v := range c.Misc { + if err := e.Encode(v); err != nil { + return err + } + } + + return nil + } +{{- end}} \ No newline at end of file diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index fef8ef54..acea588e 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -1,175 +1,228 @@ {{- if .Entry}} -package {{packageName .GoSdkPath}} - -import ( - "encoding/xml" - "fmt" - - "github.com/PaloAltoNetworks/pango/filtering" - "github.com/PaloAltoNetworks/pango/generic" - "github.com/PaloAltoNetworks/pango/util" - "github.com/PaloAltoNetworks/pango/version" -) - -var ( - _ filtering.Fielder = &Entry{} -) - -var ( - Suffix = []string{ - {{- $length := subtract (len .XpathSuffix) 1 }} - {{- range $index, $suffix := .XpathSuffix}}" - {{- $suffix}}"{{- if lt $index $length}},{{- end}} - {{- end}}} -) - -type Entry struct { + package {{packageName .GoSdkPath}} + + import ( + "encoding/xml" + "fmt" + + "github.com/PaloAltoNetworks/pango/filtering" + "github.com/PaloAltoNetworks/pango/generic" + "github.com/PaloAltoNetworks/pango/util" + "github.com/PaloAltoNetworks/pango/version" + ) + + var ( + _ filtering.Fielder = &Entry{} + ) + + var ( + Suffix = []string{ + {{- $length := subtract (len .XpathSuffix) 1 }} + {{- range $index, $suffix := .XpathSuffix}}" + {{- $suffix}}"{{- if lt $index $length}},{{- end}} + {{- end}}} + ) + + type Entry{{createGoSuffixFromVersion ""}} struct { Name string - {{- range $_, $param := .Spec.Params}} - {{$param.Name.CamelCase}} {{specParamType "" $param}} + {{- range $_, $param := $.Spec.Params}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} {{- end}} - {{- range $_, $param := .Spec.OneOf}} - {{$param.Name.CamelCase}} {{specParamType "" $param}} + {{- range $_, $param := $.Spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} {{- end}} - Misc map[string][]generic.Xml -} + Misc map[string][]generic.Xml + } -{{- range $name, $spec := nestedSpecs .Spec }} -type Spec{{$name}} struct { - {{- range $_, $param := $spec.Params}} - {{$param.Name.CamelCase}} {{specParamType $name $param}} - {{- end}} - {{- range $_, $param := $spec.OneOf}} - {{$param.Name.CamelCase}} {{specParamType $name $param}} + {{- range $name, $spec := nestedSpecs $.Spec }} + type Spec{{$name}}{{createGoSuffixFromVersion ""}} struct { + {{- range $_, $param := $spec.Params}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} + {{- end}} + } {{- end}} -} -{{- end}} -type EntryXmlContainer struct { - Answer []EntryXml `xml:"entry"` -} - -type EntryXml struct { - XMLName xml.Name `xml:"entry"` - Name string `xml:"name,attr"` - {{- range $_, $param := .Spec.Params}} - {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} - {{- end}} - {{- range $_, $param := .Spec.OneOf}} - {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} + {{- range $version := .SupportedVersions }} + type entryXmlContainer{{createGoSuffixFromVersion $version}} struct { + Answer []entryXml{{createGoSuffixFromVersion $version}} `xml:"entry"` + } {{- end}} - Misc []generic.Xml `xml:",any"` -} + {{- range $version := .SupportedVersions }} + type entryXml{{createGoSuffixFromVersion $version}} struct { + XMLName xml.Name `xml:"entry"` + Name string `xml:"name,attr"` + {{- range $_, $param := $.Spec.Params}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} -{{- range $name, $spec := nestedSpecs .Spec }} -type Spec{{$name}}Xml struct { - {{- range $_, $param := $spec.Params}} - {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} - {{- end}} - {{- range $_, $param := $spec.OneOf}} - {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} + Misc []generic.Xml `xml:",any"` + } {{- end}} -} -{{- end}} - -func (e *Entry) CopyMiscFrom(v *Entry) { - if v == nil || len(v.Misc) == 0 { - return - } - e.Misc = make(map[string][]generic.Xml) - for key := range v.Misc { - e.Misc[key] = append([]generic.Xml(nil), v.Misc[key]...) - } -} + {{- range $version := .SupportedVersions }} + {{- range $name, $spec := nestedSpecs $.Spec }} + type spec{{$name}}Xml{{createGoSuffixFromVersion $version}} struct { + {{- range $_, $param := $spec.Params}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{- if paramSupportedInVersion $param $version}} + {{- if $param.Spec}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}}{{createGoSuffixFromVersion $version}} {{xmlTag $param}} + {{- else}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} + {{- end}} + {{- end}} + {{- end}} + + Misc []generic.Xml `xml:",any"` + } + {{- end}} + {{- end}} -func (e *Entry) Field(v string) (any, error) { - if v == "name" || v == "Name" { - return e.Name, nil - } + func (e *Entry) Field(v string) (any, error) { + if v == "name" || v == "Name" { + return e.Name, nil + } {{- range $_, $param := .Spec.Params}} - if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { + if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { return e.{{$param.Name.CamelCase}}, nil - } - {{- if eq $param.Type "list"}} - if v == "{{$param.Name.Underscore}}|LENGTH" || v == "{{$param.Name.CamelCase}}|LENGTH" { - return int64(len(e.{{$param.Name.CamelCase}})), nil - } - {{- end}} + } + {{- if eq $param.Type "list"}} + if v == "{{$param.Name.Underscore}}|LENGTH" || v == "{{$param.Name.CamelCase}}|LENGTH" { + return int64(len(e.{{$param.Name.CamelCase}})), nil + } + {{- end}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { + if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { return e.{{$param.Name.CamelCase}}, nil - } + } {{- end}} - return nil, fmt.Errorf("unknown field") -} - -func Versioning(vn version.Number) (Specifier, Normalizer, error) { - return SpecifyEntry, &EntryXmlContainer{}, nil -} - -func SpecifyEntry(o Entry) (any, error) { - entry := EntryXml{} + return nil, fmt.Errorf("unknown field") + } - entry.Name = o.Name - {{- range $_, $param := .Spec.Params}} - {{specifyEntryAssignment $param}} + func Versioning(vn version.Number) (Specifier, Normalizer, error) { + {{- $numberOfVersions := len .SupportedVersions }} + {{- if gt $numberOfVersions 1}} + {{- range $index, $version := .SupportedVersions }} + {{- if ne $version ""}} + version{{createGoSuffixFromVersion $version}}, err := version.New("{{$version}}") + if err != nil { + return nil, nil, err + } + {{- end}} + {{- end}} + {{- range $index, $version := .SupportedVersions }} + {{- if ne $version ""}} + {{- if eq $index 1}} + if vn.Gte(version{{createGoSuffixFromVersion $version}}) { + return specifyEntry{{createGoSuffixFromVersion $version}}, &entryXmlContainer{{createGoSuffixFromVersion $version}}{}, nil + {{- else}} + } else if vn.Gte(version{{createGoSuffixFromVersion $version}}) { + return specifyEntry{{createGoSuffixFromVersion $version}}, &entryXmlContainer{{createGoSuffixFromVersion $version}}{}, nil + {{- end}} + {{- end}} + {{- end}} + } else { {{- end}} - {{- range $_, $param := .Spec.OneOf}} - {{specifyEntryAssignment $param}} + return specifyEntry, &entryXmlContainer{}, nil + {{- if gt $numberOfVersions 1}} + } {{- end}} + } + + {{- range $version := .SupportedVersions }} + func specifyEntry{{createGoSuffixFromVersion $version}}(o Entry) (any, error) { + entry := entryXml{{createGoSuffixFromVersion $version}}{} - entry.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] + entry.Name = o.Name + {{- range $_, $param := $.Spec.Params}} + {{specifyEntryAssignment "entry" $param $version}} + {{- end}} + {{- range $_, $param := $.Spec.OneOf}} + {{specifyEntryAssignment "entry" $param $version}} + {{- end}} - return entry, nil -} + entry.Misc = o.Misc["Entry"] -func (c *EntryXmlContainer) Normalize() ([]Entry, error) { - entryList := make([]Entry, 0, len(c.Answer)) - for _, o := range c.Answer { - entry := Entry{ - Misc: make(map[string][]generic.Xml), - } - entry.Name = o.Name - {{- range $_, $param := .Spec.Params}} - {{normalizeAssignment $param}} + return entry, nil + } + {{- end}} + + {{- range $version := .SupportedVersions }} + func (c *entryXmlContainer{{createGoSuffixFromVersion $version}}) Normalize() ([]Entry, error) { + entryList := make([]Entry, 0, len(c.Answer)) + for _, o := range c.Answer { + entry := Entry{ + Misc: make(map[string][]generic.Xml), + } + entry.Name = o.Name + {{- range $_, $param := $.Spec.Params}} + {{normalizeAssignment "entry" $param $version}} {{- end}} - {{- range $_, $param := .Spec.OneOf}} - {{normalizeAssignment $param}} + {{- range $_, $param := $.Spec.OneOf}} + {{normalizeAssignment "entry" $param $version}} {{- end}} - entry.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] = o.Misc + entry.Misc["Entry"] = o.Misc - entryList = append(entryList, entry) - } + entryList = append(entryList, entry) + } - return entryList, nil -} + return entryList, nil + } + {{- end}} -func SpecMatches(a, b *Entry) bool { - if a == nil && b != nil || a != nil && b == nil { - return false - } else if a == nil && b == nil { - return true - } + func SpecMatches(a, b *Entry) bool { + if a == nil && b != nil || a != nil && b == nil { + return false + } else if a == nil && b == nil { + return true + } - // Don't compare Name. + // Don't compare Name. {{- range $_, $param := .Spec.Params}} - if !util.{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { + if !{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { return false - } + } {{- end}} {{- range $_, $param := .Spec.OneOf}} - if !util.{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { + if !{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { return false - } + } {{- end}} - return true -} + return true + } + + {{nestedSpecMatchesFunction $.Spec}} {{- end}} \ No newline at end of file diff --git a/templates/sdk/interfaces.tmpl b/templates/sdk/interfaces.tmpl index c886baa1..f8a0175f 100644 --- a/templates/sdk/interfaces.tmpl +++ b/templates/sdk/interfaces.tmpl @@ -1,7 +1,15 @@ package {{packageName .GoSdkPath}} -type Specifier func(Entry) (any, error) +{{- if .Entry}} + type Specifier func(Entry) (any, error) +{{- else}} + type Specifier func(Config) (any, error) +{{- end}} type Normalizer interface { +{{- if .Entry}} Normalize() ([]Entry, error) +{{- else}} + Normalize() ([]Config, error) +{{- end}} } \ No newline at end of file diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index 121125cf..e23bebde 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -1,80 +1,87 @@ package {{packageName .GoSdkPath}} import ( - "fmt" +"fmt" - "github.com/PaloAltoNetworks/pango/errors" - "github.com/PaloAltoNetworks/pango/util" - "github.com/PaloAltoNetworks/pango/version" +"github.com/PaloAltoNetworks/pango/errors" +"github.com/PaloAltoNetworks/pango/util" +"github.com/PaloAltoNetworks/pango/version" ) type Location struct { - {{range $key, $location := .Locations}} +{{range $key, $location := .Locations}} {{- $location.Name.CamelCase }} {{locationType $location true}} `json:"{{$location.Name.Underscore}}{{omitEmpty $location}}"` - {{end}} +{{end}} } {{range $key, $location := .Locations}} -{{- if $location.Vars}} -type {{locationType $location false}} struct { -{{- range $key, $var := $location.Vars}} - {{$var.Name.CamelCase}} string `json:"{{$var.Name.Underscore}}"` -{{- end}} -} -{{end}} + {{- if $location.Vars}} + type {{locationType $location false}} struct { + {{- range $key, $var := $location.Vars}} + {{$var.Name.CamelCase}} string `json:"{{$var.Name.Underscore}}"` + {{- end}} + } + {{end}} {{- end}} func (o Location) IsValid() error { - count := 0 +count := 0 - switch { - {{- range $key, $location := .Locations}} +switch { +{{- range $key, $location := .Locations}} case o.{{- $location.Name.CamelCase}}{{if ne (locationType $location true) "bool"}} != nil{{end}}: - {{- range $name, $var := $location.Vars}} + {{- range $name, $var := $location.Vars}} if o.{{$location.Name.CamelCase}}.{{$var.Name.CamelCase}} == "" { - return fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") + return fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") } - {{- end}} - count++ {{- end}} - } + count++ +{{- end}} +} - if count == 0 { - return fmt.Errorf("no path specified") - } +if count == 0 { +return fmt.Errorf("no path specified") +} - if count > 1 { - return fmt.Errorf("multiple paths specified: only one should be specified") - } +if count > 1 { +return fmt.Errorf("multiple paths specified: only one should be specified") +} - return nil +return nil } -func (o Location) Xpath(vn version.Number, name string) ([]string, error) { +{{- if .Entry}} + func (o Location) Xpath(vn version.Number, name string) ([]string, error) { +{{- else}} + func (o Location) Xpath(vn version.Number) ([]string, error) { +{{- end}} - var ans []string +var ans []string - switch { - {{- range $key, $location := .Locations}} +switch { +{{- range $key, $location := .Locations}} case o.{{- $location.Name.CamelCase}}{{if ne (locationType $location true) "bool"}} != nil{{end}}: - {{- range $name, $var := $location.Vars}} + {{- range $name, $var := $location.Vars}} if o.{{$location.Name.CamelCase}}.{{$var.Name.CamelCase}} == "" { - return nil, fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") + return nil, fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") } - {{- end}} - ans = []string{ - {{- range $name, $xpath := $location.Xpath}} - {{- if contains $xpath "Entry"}} + {{- end}} + ans = []string{ + {{- range $name, $xpath := $location.Xpath}} + {{- if contains $xpath "Entry"}} {{generateEntryXpath $location.Name.CamelCase $xpath}} - {{- else}} + {{- else}} "{{$xpath}}", - {{- end}} - {{- end}} - } + {{- end}} {{- end}} - default: - return nil, errors.NoLocationSpecifiedError - } + } +{{- end}} +default: +return nil, errors.NoLocationSpecifiedError +} - ans = append(ans, util.AsEntryXpath([]string{name})) +{{- if .Entry}} + ans = append(ans, Suffix...) + ans = append(ans, util.AsEntryXpath([]string{name})) +{{- end}} - return ans, nil +return ans, nil } \ No newline at end of file