From 2155e70b6bc83489b65f788e228307219ce1edb6 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 4 Mar 2024 16:19:26 +0100 Subject: [PATCH 01/32] new function to make indentation equal in list of strings --- pkg/translate/names.go | 21 ++++++++++++++++++++- pkg/translate/names_test.go | 12 ++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg/translate/names.go b/pkg/translate/names.go index d584fce1..bd9133ee 100644 --- a/pkg/translate/names.go +++ b/pkg/translate/names.go @@ -1,9 +1,28 @@ package translate -// Get package name from Go SDK path +import "strings" + +// PackageName Get package name from Go SDK path func PackageName(list []string) string { if len(list) == 0 { return "" } return list[len(list)-1] } + +// MakeIndentationEqual Check max lenght of the string in the list and then add spaces at the end of very name to make equal indentation +func MakeIndentationEqual(list []string) []string { + maxLength := 0 + + for _, str := range list { + if len(str) > maxLength { + maxLength = len(str) + } + } + + for idx, str := range list { + list[idx] = str + strings.Repeat(" ", maxLength-len(str)) + } + + return list +} diff --git a/pkg/translate/names_test.go b/pkg/translate/names_test.go index f0a7c2cc..8f3a6738 100644 --- a/pkg/translate/names_test.go +++ b/pkg/translate/names_test.go @@ -15,3 +15,15 @@ func TestPackageName(t *testing.T) { // then assert.Equal(t, "address", packageName) } + +func TestMakeIndentationEqual(t *testing.T) { + // given + givenItems := []string{"test", "a"} + exptectedItems := []string{"test", "a "} + + // when + changedItems := MakeIndentationEqual(givenItems) + + // then + assert.Equal(t, exptectedItems, changedItems) +} From d5ffed6cac71767eedaa0458c222cace4adcda1e Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 4 Mar 2024 16:19:41 +0100 Subject: [PATCH 02/32] execute new function creating definition of structs --- pkg/generate/generator.go | 3 ++- templates/sdk/location.tmpl | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index fa67a141..7d35bf47 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -75,7 +75,8 @@ func (c *Creator) generateOutputFileFromTemplate(tmpl *template.Template, output func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := fmt.Sprintf("%s/%s", c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, + "packageName": translate.PackageName, + "structsDefinitionsForLocation": translate.StructsDefinitionsForLocation, } tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) if err != nil { diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index 0c27f6b2..510ac88b 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -7,3 +7,5 @@ import ( "github.com/PaloAltoNetworks/pango/util" "github.com/PaloAltoNetworks/pango/version" ) + +{{ structsDefinitionsForLocation .Locations }} \ No newline at end of file From dcb8f2eab2c1fc6257fd9183f2e6102b4aa2288a Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 4 Mar 2024 16:20:00 +0100 Subject: [PATCH 03/32] new function creating definition of structs (with tests) --- pkg/translate/structs.go | 55 +++++++++++++ pkg/translate/structs_test.go | 149 ++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 pkg/translate/structs.go create mode 100644 pkg/translate/structs_test.go diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go new file mode 100644 index 00000000..405f8c91 --- /dev/null +++ b/pkg/translate/structs.go @@ -0,0 +1,55 @@ +package translate + +import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "strings" +) + +func StructsDefinitionsForLocation(locations map[string]*properties.Location) (string, error) { + var builder strings.Builder + + builder.WriteString("type Location struct {\n") + for name := range locations { + switch name { + case "shared": + builder.WriteString("\tShared bool `json:\"shared\"`\n") + case "from_panorama": + builder.WriteString("\tFromPanorama bool `json:\"from_panorama\"`\n") + case "vsys": + builder.WriteString("\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n") + case "device_group": + builder.WriteString("\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n") + } + } + builder.WriteString("}\n\n") + + if _, ok := locations["vsys"]; ok { + var vars []string + for name := range locations["vsys"].Vars { + vars = append(vars, name) + } + vars = MakeIndentationEqual(vars) + + builder.WriteString("type VsysLocation struct {\n") + for _, name := range vars { + builder.WriteString("\t" + name + "\tstring `json:\"" + name + "\"`\n") + } + builder.WriteString("}\n\n") + } + + if _, ok := locations["device_group"]; ok { + var vars []string + for name := range locations["device_group"].Vars { + vars = append(vars, name) + } + vars = MakeIndentationEqual(vars) + + builder.WriteString("type DeviceGroupLocation struct {\n") + for _, name := range vars { + builder.WriteString("\t" + name + "\tstring `json:\"" + strings.TrimSpace(name) + "\"`\n") + } + builder.WriteString("}\n\n") + } + + return builder.String(), nil +} diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go new file mode 100644 index 00000000..0fb8e731 --- /dev/null +++ b/pkg/translate/structs_test.go @@ -0,0 +1,149 @@ +package translate + +import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStructsDefinitionsForLocation(t *testing.T) { + // given + var sampleSpec = `name: 'Address' +terraform_provider_suffix: 'address' +go_sdk_path: + - 'objects' + - 'address' +xpath_suffix: + - 'address' +locations: + 'shared': + description: 'Located in shared.' + device: + panorama: true + ngfw: true + xpath: ['config', 'shared'] + 'from_panorama': + description: 'Located in the config pushed from Panorama.' + read_only: true + device: + ngfw: true + xpath: ['config', 'panorama'] + 'vsys': + description: 'Located in a specific vsys.' + device: + panorama: true + ngfw: true + xpath: + - 'config' + - 'devices' + - '{{ Entry $ngfw_device }}' + - 'vsys' + - '{{ Entry $vsys }}' + vars: + 'ngfw_device': + description: 'The NGFW device.' + default: 'localhost.localdomain' + 'vsys': + description: 'The vsys.' + default: 'vsys1' + validation: + not_values: + 'shared': 'The vsys cannot be "shared". Use the "shared" path instead.' + 'device_group': + description: 'Located in a specific device group.' + device: + panorama: true + xpath: + - 'config' + - 'devices' + - '{{ Entry $panorama_device }}' + - 'device-group' + - '{{ Entry $device_group }}' + vars: + 'panorama_device': + description: 'The panorama device.' + default: 'localhost.localdomain' + 'device_group': + description: 'The device group.' + required: true + validation: + not_values: + 'shared': 'The device group cannot be "shared". Use the "shared" path instead.' +entry: + name: + description: 'The name of the address object.' + length: + min: 1 + max: 63 +version: '10.1.0' +spec: + params: + description: + description: 'The description.' + type: 'string' + length: + min: 0 + max: 1023 + profiles: + - + xpath: ["description"] + tags: + description: 'The administrative tags.' + type: 'list' + count: + max: 64 + items: + type: 'string' + length: + max: 127 + profiles: + - + type: 'member' + xpath: ["tag"] + one_of: + 'ip_netmask': + description: 'The IP netmask value.' + profiles: + - + xpath: ["ip-netmask"] + 'ip_range': + description: 'The IP range value.' + profiles: + - + xpath: ["ip-range"] + 'fqdn': + description: 'The FQDN value.' + regex: '^[a-zA-Z0-9_]([a-zA-Z0-9:_-])+[a-zA-Z0-9]$' + length: + min: 1 + max: 255 + profiles: + - + xpath: ["fqdn"] + 'ip_wildcard': + description: 'The IP wildcard value.' + profiles: + - + xpath: ["ip-wildcard"] +` + var expectedStructsForLocation = "type Location struct {\n" + + "\tShared bool `json:\"shared\"`\n" + + "\tFromPanorama bool `json:\"from_panorama\"`\n" + + "\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n" + + "\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n" + + "}\n" + + "\ntype VsysLocation struct {\n" + + "\tngfw_device\tstring `json:\"ngfw_device\"`\n" + + "\tvsys \tstring `json:\"vsys \"`\n}\n" + + "\ntype DeviceGroupLocation struct {\n" + + "\tpanorama_device\tstring `json:\"panorama_device\"`\n" + + "\tdevice_group \tstring `json:\"device_group\"`\n" + + "}\n\n" + + // when + yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec)) + structsForLocation, _ := StructsDefinitionsForLocation(yamlParsedData.Locations) + + // then + assert.Equal(t, expectedStructsForLocation, structsForLocation) +} From 7c5f19ba633f6c8a6fb1ab482da34c2951692aca Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 4 Mar 2024 16:31:41 +0100 Subject: [PATCH 04/32] fix logic after tests --- pkg/translate/structs.go | 23 +++++++++++++---------- pkg/translate/structs_test.go | 8 ++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 405f8c91..175f87ad 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -1,6 +1,7 @@ package translate import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "strings" ) @@ -24,29 +25,31 @@ func StructsDefinitionsForLocation(locations map[string]*properties.Location) (s builder.WriteString("}\n\n") if _, ok := locations["vsys"]; ok { - var vars []string + var namesOriginal, namesCamelCaseWithSpaces []string for name := range locations["vsys"].Vars { - vars = append(vars, name) + namesOriginal = append(namesOriginal, name) + namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) } - vars = MakeIndentationEqual(vars) + namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) builder.WriteString("type VsysLocation struct {\n") - for _, name := range vars { - builder.WriteString("\t" + name + "\tstring `json:\"" + name + "\"`\n") + for idx, name := range namesCamelCaseWithSpaces { + builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") } builder.WriteString("}\n\n") } if _, ok := locations["device_group"]; ok { - var vars []string + var namesOriginal, namesCamelCaseWithSpaces []string for name := range locations["device_group"].Vars { - vars = append(vars, name) + namesOriginal = append(namesOriginal, name) + namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) } - vars = MakeIndentationEqual(vars) + namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) builder.WriteString("type DeviceGroupLocation struct {\n") - for _, name := range vars { - builder.WriteString("\t" + name + "\tstring `json:\"" + strings.TrimSpace(name) + "\"`\n") + for idx, name := range namesCamelCaseWithSpaces { + builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") } builder.WriteString("}\n\n") } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 0fb8e731..bf7bbe40 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -133,11 +133,11 @@ spec: "\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n" + "}\n" + "\ntype VsysLocation struct {\n" + - "\tngfw_device\tstring `json:\"ngfw_device\"`\n" + - "\tvsys \tstring `json:\"vsys \"`\n}\n" + + "\tNgfwDevice string `json:\"ngfw_device\"`\n" + + "\tVsys string `json:\"vsys\"`\n}\n" + "\ntype DeviceGroupLocation struct {\n" + - "\tpanorama_device\tstring `json:\"panorama_device\"`\n" + - "\tdevice_group \tstring `json:\"device_group\"`\n" + + "\tPanoramaDevice string `json:\"panorama_device\"`\n" + + "\tDeviceGroup string `json:\"device_group\"`\n" + "}\n\n" // when From 81718270936bcb578ac9be8fd2292ead093e7288 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 4 Mar 2024 16:38:57 +0100 Subject: [PATCH 05/32] refactor code (remove duplication and extract func) --- pkg/translate/structs.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 175f87ad..7684f2a4 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -24,35 +24,25 @@ func StructsDefinitionsForLocation(locations map[string]*properties.Location) (s } builder.WriteString("}\n\n") - if _, ok := locations["vsys"]; ok { - var namesOriginal, namesCamelCaseWithSpaces []string - for name := range locations["vsys"].Vars { - namesOriginal = append(namesOriginal, name) - namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) - } - namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) + nestedStructsDefinitionsForLocation(locations, "vsys", "VsysLocation", &builder) + nestedStructsDefinitionsForLocation(locations, "device_group", "DeviceGroupLocation", &builder) - builder.WriteString("type VsysLocation struct {\n") - for idx, name := range namesCamelCaseWithSpaces { - builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") - } - builder.WriteString("}\n\n") - } + return builder.String(), nil +} - if _, ok := locations["device_group"]; ok { +func nestedStructsDefinitionsForLocation(locations map[string]*properties.Location, locationName string, structName string, builder *strings.Builder) { + if _, ok := locations[locationName]; ok { var namesOriginal, namesCamelCaseWithSpaces []string - for name := range locations["device_group"].Vars { + for name := range locations[locationName].Vars { namesOriginal = append(namesOriginal, name) namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) } namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) - builder.WriteString("type DeviceGroupLocation struct {\n") + builder.WriteString("type " + structName + " struct {\n") for idx, name := range namesCamelCaseWithSpaces { builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") } builder.WriteString("}\n\n") } - - return builder.String(), nil } From 7aad15572668ba59b56183c944b794947d9eaf7e Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 09:05:29 +0100 Subject: [PATCH 06/32] fix random order in keys for maps --- pkg/translate/structs.go | 18 +++++++++++++++--- pkg/translate/structs_test.go | 6 +++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 7684f2a4..5e686be8 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -3,14 +3,20 @@ package translate import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "sort" "strings" ) func StructsDefinitionsForLocation(locations map[string]*properties.Location) (string, error) { - var builder strings.Builder + keys := make([]string, 0, len(locations)) + for key := range locations { + keys = append(keys, key) + } + sort.Strings(keys) + var builder strings.Builder builder.WriteString("type Location struct {\n") - for name := range locations { + for _, name := range keys { switch name { case "shared": builder.WriteString("\tShared bool `json:\"shared\"`\n") @@ -32,8 +38,14 @@ func StructsDefinitionsForLocation(locations map[string]*properties.Location) (s func nestedStructsDefinitionsForLocation(locations map[string]*properties.Location, locationName string, structName string, builder *strings.Builder) { if _, ok := locations[locationName]; ok { + keys := make([]string, 0, len(locations[locationName].Vars)) + for key := range locations[locationName].Vars { + keys = append(keys, key) + } + sort.Strings(keys) + var namesOriginal, namesCamelCaseWithSpaces []string - for name := range locations[locationName].Vars { + for _, name := range keys { namesOriginal = append(namesOriginal, name) namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index bf7bbe40..4a4b38bd 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -127,17 +127,17 @@ spec: xpath: ["ip-wildcard"] ` var expectedStructsForLocation = "type Location struct {\n" + - "\tShared bool `json:\"shared\"`\n" + + "\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n" + "\tFromPanorama bool `json:\"from_panorama\"`\n" + + "\tShared bool `json:\"shared\"`\n" + "\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n" + - "\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n" + "}\n" + "\ntype VsysLocation struct {\n" + "\tNgfwDevice string `json:\"ngfw_device\"`\n" + "\tVsys string `json:\"vsys\"`\n}\n" + "\ntype DeviceGroupLocation struct {\n" + - "\tPanoramaDevice string `json:\"panorama_device\"`\n" + "\tDeviceGroup string `json:\"device_group\"`\n" + + "\tPanoramaDevice string `json:\"panorama_device\"`\n" + "}\n\n" // when From b2bef135d6320b85c13b14eb3bf1e2a7d21d8a51 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 09:14:39 +0100 Subject: [PATCH 07/32] update testify package --- go.mod | 2 +- go.sum | 4 ++-- pkg/translate/names_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f5ca045f..254507b4 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/paloaltonetworks/pan-os-codegen require ( - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index fa4b6e68..60ce688a 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/translate/names_test.go b/pkg/translate/names_test.go index 8f3a6738..0b4e5d6a 100644 --- a/pkg/translate/names_test.go +++ b/pkg/translate/names_test.go @@ -19,11 +19,11 @@ func TestPackageName(t *testing.T) { func TestMakeIndentationEqual(t *testing.T) { // given givenItems := []string{"test", "a"} - exptectedItems := []string{"test", "a "} + expectedItems := []string{"test", "a "} // when changedItems := MakeIndentationEqual(givenItems) // then - assert.Equal(t, exptectedItems, changedItems) + assert.Equal(t, expectedItems, changedItems) } From cd267f23fdf268eedd255e93c6c72460ac561ca7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 10:40:20 +0100 Subject: [PATCH 08/32] new functions to generate functions for locations --- pkg/translate/funcs.go | 150 ++++++++++++++++++++++++++++++++++++ pkg/translate/funcs_test.go | 1 + 2 files changed, 151 insertions(+) create mode 100644 pkg/translate/funcs.go create mode 100644 pkg/translate/funcs_test.go diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go new file mode 100644 index 00000000..10a87a5b --- /dev/null +++ b/pkg/translate/funcs.go @@ -0,0 +1,150 @@ +package translate + +import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "sort" + "strings" +) + +func FuncBodyForLocation(locations map[string]*properties.Location) (string, error) { + keys := make([]string, 0, len(locations)) + for key := range locations { + keys = append(keys, key) + } + sort.Strings(keys) + + mapIsValid := map[string]string{ + "shared": ` + case o.Shared: + count++ +`, + + "from_panorama": ` + case o.FromPanorama: + count++ +`, + + "vsys": ` + case o.Vsys != nil: + if o.Vsys.Vsys == "" { + return fmt.Errorf("vsys.vsys is unspecified") + } + if o.Vsys.NgfwDevice == "" { + return fmt.Errorf("vsys.ngfw_device is unspecified") + } + count++ +`, + + "device_group": ` + case o.DeviceGroup != nil: + if o.DeviceGroup.DeviceGroup == "" { + return fmt.Errorf("device_group.device_group is unspecified") + } + if o.DeviceGroup.PanoramaDevice == "" { + return fmt.Errorf("device_group.panorama_device is unspecified") + } + count++ +`, + } + mapXpath := map[string]string{ + "shared": ` + case o.Shared: + ans = []string{ + "config", + "shared", + } +`, + + "from_panorama": ` + case o.FromPanorama: + ans = []string{"config", "panorama"} +`, + + "vsys": ` + case o.Vsys != nil: + if o.Vsys.NgfwDevice == "" { + return nil, fmt.Errorf("NgfwDevice is unspecified") + } + if o.Vsys.Vsys == "" { + return nil, fmt.Errorf("Vsys is unspecified") + } + ans = []string{ + "config", + "devices", + util.AsEntryXpath([]string{o.Vsys.NgfwDevice}), + "vsys", + util.AsEntryXpath([]string{o.Vsys.Vsys}), + } +`, + + "device_group": ` + case o.DeviceGroup != nil: + if o.DeviceGroup.PanoramaDevice == "" { + return nil, fmt.Errorf("PanoramaDevice is unspecified") + } + if o.DeviceGroup.DeviceGroup == "" { + return nil, fmt.Errorf("DeviceGroup is unspecified") + } + ans = []string{ + "config", + "devices", + util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}), + "device-group", + util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}), + } +`, + } + + var builder strings.Builder + funcBodyForLocation(&builder, keys, "IsValid", "()", "error", false, + ` count := 0 + + switch { +`, + ` } + + if count == 0 { + return fmt.Errorf("no path specified") + } + + if count > 1 { + return fmt.Errorf("multiple paths specified: only one should be specified") + } + + return nil +`, + mapIsValid) + funcBodyForLocation(&builder, keys, "Xpath", "(vn version.Number, name string)", "([]string, error)", + true, ` + var ans []string + + switch { +`, + ` + default: + return nil, errors.NoLocationSpecifiedError + } + + ans = append(ans, Suffix...) + ans = append(ans, util.AsEntryXpath([]string{name})) + + return ans, nil +`, + mapXpath) + + return builder.String(), nil +} + +func funcBodyForLocation(builder *strings.Builder, keys []string, funcName string, funcInput string, funcOutput string, + startFromNewLine bool, funcBegin string, funcEnd string, funcCases map[string]string) { + if startFromNewLine { + builder.WriteString("\n") + } + builder.WriteString("func (o Location) " + funcName + funcInput + " " + funcOutput + " {\n") + builder.WriteString(funcBegin) + for _, name := range keys { + builder.WriteString(funcCases[name]) + } + builder.WriteString(funcEnd) + builder.WriteString("}\n") +} diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go new file mode 100644 index 00000000..c2cb5d52 --- /dev/null +++ b/pkg/translate/funcs_test.go @@ -0,0 +1 @@ +package translate From e975329f07d336c29e1a928d183a3cbf03ec291a Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 10:40:37 +0100 Subject: [PATCH 09/32] use new functions for locations --- pkg/generate/generator.go | 1 + pkg/translate/structs.go | 8 ++++---- pkg/translate/structs_test.go | 10 +++++----- templates/sdk/location.tmpl | 3 ++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 7d35bf47..8dcc5d33 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -77,6 +77,7 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) funcMap := template.FuncMap{ "packageName": translate.PackageName, "structsDefinitionsForLocation": translate.StructsDefinitionsForLocation, + "funcBodyForLocation": translate.FuncBodyForLocation, } tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) if err != nil { diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 5e686be8..fb96eb85 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -28,7 +28,7 @@ func StructsDefinitionsForLocation(locations map[string]*properties.Location) (s builder.WriteString("\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n") } } - builder.WriteString("}\n\n") + builder.WriteString("}\n") nestedStructsDefinitionsForLocation(locations, "vsys", "VsysLocation", &builder) nestedStructsDefinitionsForLocation(locations, "device_group", "DeviceGroupLocation", &builder) @@ -51,10 +51,10 @@ func nestedStructsDefinitionsForLocation(locations map[string]*properties.Locati } namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) - builder.WriteString("type " + structName + " struct {\n") + builder.WriteString("\ntype " + structName + " struct {\n") for idx, name := range namesCamelCaseWithSpaces { - builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") + builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") } - builder.WriteString("}\n\n") + builder.WriteString("}\n") } } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 4a4b38bd..6a2f4071 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -133,12 +133,12 @@ spec: "\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n" + "}\n" + "\ntype VsysLocation struct {\n" + - "\tNgfwDevice string `json:\"ngfw_device\"`\n" + - "\tVsys string `json:\"vsys\"`\n}\n" + + "\tNgfwDevice string `json:\"ngfw_device\"`\n" + + "\tVsys string `json:\"vsys\"`\n}\n" + "\ntype DeviceGroupLocation struct {\n" + - "\tDeviceGroup string `json:\"device_group\"`\n" + - "\tPanoramaDevice string `json:\"panorama_device\"`\n" + - "}\n\n" + "\tDeviceGroup string `json:\"device_group\"`\n" + + "\tPanoramaDevice string `json:\"panorama_device\"`\n" + + "}\n" // when yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec)) diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index 510ac88b..f7e6a381 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -8,4 +8,5 @@ import ( "github.com/PaloAltoNetworks/pango/version" ) -{{ structsDefinitionsForLocation .Locations }} \ No newline at end of file +{{ structsDefinitionsForLocation .Locations }} +{{ funcBodyForLocation .Locations }} \ No newline at end of file From b5095ac82fd160633c2ed6c1bd1bc10acce7fa0f Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 11:54:20 +0100 Subject: [PATCH 10/32] extend template, do not concat strings to generate structs --- pkg/generate/generator.go | 4 ++++ pkg/translate/structs.go | 20 ++++++++++++++++++++ templates/sdk/location.tmpl | 18 ++++++++++++++++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 8dcc5d33..cc039df0 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -2,6 +2,7 @@ package generate 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/translate" "io" @@ -78,6 +79,9 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) "packageName": translate.PackageName, "structsDefinitionsForLocation": translate.StructsDefinitionsForLocation, "funcBodyForLocation": translate.FuncBodyForLocation, + "camelCase": naming.CamelCase, + "locationType": translate.LocationType, + "omitEmpty": translate.OmitEmpty, } tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) if err != nil { diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index fb96eb85..35d6e02e 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -7,6 +7,26 @@ import ( "strings" ) +func LocationType(name string, location properties.Location, pointer bool) string { + prefix := "" + if pointer { + prefix = "*" + } + if location.Vars != nil { + return prefix + naming.CamelCase("", name, "", true) + "Location" + } else { + return "bool" + } +} + +func OmitEmpty(location properties.Location) string { + if location.Vars != nil { + return ",omitempty" + } else { + return "" + } +} + func StructsDefinitionsForLocation(locations map[string]*properties.Location) (string, error) { keys := make([]string, 0, len(locations)) for key := range locations { diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index f7e6a381..b6024a81 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -1,4 +1,4 @@ -package {{ packageName .GoSdkPath }} +package {{packageName .GoSdkPath }} import ( "fmt" @@ -8,5 +8,19 @@ import ( "github.com/PaloAltoNetworks/pango/version" ) -{{ structsDefinitionsForLocation .Locations }} +type Location struct { + {{range $key, $location := .Locations}} + {{- camelCase "" $key "" true}} {{locationType $key $location true}} `json:"{{$key}}{{omitEmpty $location}}"` + {{end}} +} +{{range $key, $location := .Locations}} +{{- if $location.Vars}} +type {{locationType $key $location false}} struct { +{{- range $key, $var := $location.Vars}} + {{camelCase "" $key "" true}} string `json:"{{$key}}"` +{{- end}} +} +{{- end}} +{{- end}} + {{ funcBodyForLocation .Locations }} \ No newline at end of file From a07ae67955b19770dfdc169735b2cacce6f5677f Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 12:58:39 +0100 Subject: [PATCH 11/32] extend template, do not concat strings to generate functions --- pkg/generate/generator.go | 12 +++++-- pkg/translate/funcs.go | 8 +++++ templates/sdk/location.tmpl | 62 +++++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index cc039df0..8770dfb8 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -79,9 +79,15 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) "packageName": translate.PackageName, "structsDefinitionsForLocation": translate.StructsDefinitionsForLocation, "funcBodyForLocation": translate.FuncBodyForLocation, - "camelCase": naming.CamelCase, - "locationType": translate.LocationType, - "omitEmpty": translate.OmitEmpty, + "camelCase": func(name string) string { + return naming.CamelCase("", name, "", true) + }, + "locationType": translate.LocationType, + "omitEmpty": translate.OmitEmpty, + "contains": func(full, part string) bool { + return strings.Contains(full, part) + }, + "asEntryXpath": translate.AsEntryXpath, } tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) if err != nil { diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 10a87a5b..36a3212f 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -1,11 +1,19 @@ package translate import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "sort" "strings" ) +func AsEntryXpath(location, xpath string) string { + location = naming.CamelCase("", location, "", true) + xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) + xpath = naming.CamelCase("", xpath, "", true) + return "util.AsEntryXpath([]string{o." + location + "." + xpath + "})," +} + func FuncBodyForLocation(locations map[string]*properties.Location) (string, error) { keys := make([]string, 0, len(locations)) for key := range locations { diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index b6024a81..b229a1c2 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -10,17 +10,73 @@ import ( type Location struct { {{range $key, $location := .Locations}} - {{- camelCase "" $key "" true}} {{locationType $key $location true}} `json:"{{$key}}{{omitEmpty $location}}"` + {{- camelCase $key}} {{locationType $key $location true}} `json:"{{$key}}{{omitEmpty $location}}"` {{end}} } {{range $key, $location := .Locations}} {{- if $location.Vars}} type {{locationType $key $location false}} struct { {{- range $key, $var := $location.Vars}} - {{camelCase "" $key "" true}} string `json:"{{$key}}"` + {{camelCase $key}} string `json:"{{$key}}"` {{- end}} } {{- end}} {{- end}} -{{ funcBodyForLocation .Locations }} \ No newline at end of file + +func (o Location) IsValid() error { + count := 0 + + switch { + {{- range $key, $location := .Locations}} + case o.{{- camelCase $key}}{{if ne (locationType $key $location true) "bool"}} != nil{{end}}: + {{- range $name, $var := $location.Vars}} + if o.{{camelCase $key}}.{{camelCase $name}} == "" { + return fmt.Errorf("{{camelCase $name}} is unspecified") + } + {{- end}} + count++ + {{- end}} + } + + if count == 0 { + return fmt.Errorf("no path specified") + } + + if count > 1 { + return fmt.Errorf("multiple paths specified: only one should be specified") + } + + return nil +} + +func (o Location) Xpath(vn version.Number, name string) ([]string, error) { + + var ans []string + + switch { + {{- range $key, $location := .Locations}} + case o.{{- camelCase $key}}{{if ne (locationType $key $location true) "bool"}} != nil{{end}}: + {{- range $name, $var := $location.Vars}} + if o.{{camelCase $key}}.{{camelCase $name}} == "" { + return nil, fmt.Errorf("{{camelCase $name}} is unspecified") + } + {{- end}} + ans = []string{ + {{- range $name, $xpath := $location.Xpath}} + {{- if contains $xpath "Entry"}} + {{ asEntryXpath $key $xpath }} + {{- else}} + "{{$xpath}}", + {{- end}} + {{- end}} + } + {{- end}} + default: + return nil, errors.NoLocationSpecifiedError + } + + ans = append(ans, util.AsEntryXpath([]string{name})) + + return ans, nil +} \ No newline at end of file From 2b7514c10f44eeb3e2fd6bb567ec8805ca1e37c3 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 13:07:38 +0100 Subject: [PATCH 12/32] clean code --- pkg/generate/generator.go | 4 +- pkg/generate/generator_test.go | 2 +- pkg/translate/funcs.go | 145 --------------------------------- pkg/translate/structs.go | 54 ------------ 4 files changed, 2 insertions(+), 203 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 8770dfb8..0e433122 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -76,9 +76,7 @@ func (c *Creator) generateOutputFileFromTemplate(tmpl *template.Template, output func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := fmt.Sprintf("%s/%s", c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, - "structsDefinitionsForLocation": translate.StructsDefinitionsForLocation, - "funcBodyForLocation": translate.FuncBodyForLocation, + "packageName": translate.PackageName, "camelCase": func(name string) string { return naming.CamelCase("", name, "", true) }, diff --git a/pkg/generate/generator_test.go b/pkg/generate/generator_test.go index 816cc90e..51de2e59 100644 --- a/pkg/generate/generator_test.go +++ b/pkg/generate/generator_test.go @@ -44,7 +44,7 @@ func TestListOfTemplates(t *testing.T) { assert.Equal(t, 4, len(templates)) } -func TestParseTemplate(t *testing.T) { +func TestParseTemplateForInterfaces(t *testing.T) { // given spec := properties.Normalization{ GoSdkPath: []string{"object", "address"}, diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 36a3212f..9f507c2c 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -2,8 +2,6 @@ package translate import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" - "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" - "sort" "strings" ) @@ -13,146 +11,3 @@ func AsEntryXpath(location, xpath string) string { xpath = naming.CamelCase("", xpath, "", true) return "util.AsEntryXpath([]string{o." + location + "." + xpath + "})," } - -func FuncBodyForLocation(locations map[string]*properties.Location) (string, error) { - keys := make([]string, 0, len(locations)) - for key := range locations { - keys = append(keys, key) - } - sort.Strings(keys) - - mapIsValid := map[string]string{ - "shared": ` - case o.Shared: - count++ -`, - - "from_panorama": ` - case o.FromPanorama: - count++ -`, - - "vsys": ` - case o.Vsys != nil: - if o.Vsys.Vsys == "" { - return fmt.Errorf("vsys.vsys is unspecified") - } - if o.Vsys.NgfwDevice == "" { - return fmt.Errorf("vsys.ngfw_device is unspecified") - } - count++ -`, - - "device_group": ` - case o.DeviceGroup != nil: - if o.DeviceGroup.DeviceGroup == "" { - return fmt.Errorf("device_group.device_group is unspecified") - } - if o.DeviceGroup.PanoramaDevice == "" { - return fmt.Errorf("device_group.panorama_device is unspecified") - } - count++ -`, - } - mapXpath := map[string]string{ - "shared": ` - case o.Shared: - ans = []string{ - "config", - "shared", - } -`, - - "from_panorama": ` - case o.FromPanorama: - ans = []string{"config", "panorama"} -`, - - "vsys": ` - case o.Vsys != nil: - if o.Vsys.NgfwDevice == "" { - return nil, fmt.Errorf("NgfwDevice is unspecified") - } - if o.Vsys.Vsys == "" { - return nil, fmt.Errorf("Vsys is unspecified") - } - ans = []string{ - "config", - "devices", - util.AsEntryXpath([]string{o.Vsys.NgfwDevice}), - "vsys", - util.AsEntryXpath([]string{o.Vsys.Vsys}), - } -`, - - "device_group": ` - case o.DeviceGroup != nil: - if o.DeviceGroup.PanoramaDevice == "" { - return nil, fmt.Errorf("PanoramaDevice is unspecified") - } - if o.DeviceGroup.DeviceGroup == "" { - return nil, fmt.Errorf("DeviceGroup is unspecified") - } - ans = []string{ - "config", - "devices", - util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}), - "device-group", - util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}), - } -`, - } - - var builder strings.Builder - funcBodyForLocation(&builder, keys, "IsValid", "()", "error", false, - ` count := 0 - - switch { -`, - ` } - - if count == 0 { - return fmt.Errorf("no path specified") - } - - if count > 1 { - return fmt.Errorf("multiple paths specified: only one should be specified") - } - - return nil -`, - mapIsValid) - funcBodyForLocation(&builder, keys, "Xpath", "(vn version.Number, name string)", "([]string, error)", - true, ` - var ans []string - - switch { -`, - ` - default: - return nil, errors.NoLocationSpecifiedError - } - - ans = append(ans, Suffix...) - ans = append(ans, util.AsEntryXpath([]string{name})) - - return ans, nil -`, - mapXpath) - - return builder.String(), nil -} - -func funcBodyForLocation(builder *strings.Builder, keys []string, funcName string, funcInput string, funcOutput string, - startFromNewLine bool, funcBegin string, funcEnd string, funcCases map[string]string) { - if startFromNewLine { - builder.WriteString("\n") - } - builder.WriteString("func (o Location) " + funcName + funcInput + " " + funcOutput + " {\n") - builder.WriteString(funcBegin) - for _, name := range keys { - builder.WriteString(funcCases[name]) - } - builder.WriteString(funcEnd) - builder.WriteString("}\n") -} diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 35d6e02e..ce477977 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -3,8 +3,6 @@ package translate import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" - "sort" - "strings" ) func LocationType(name string, location properties.Location, pointer bool) string { @@ -26,55 +24,3 @@ func OmitEmpty(location properties.Location) string { return "" } } - -func StructsDefinitionsForLocation(locations map[string]*properties.Location) (string, error) { - keys := make([]string, 0, len(locations)) - for key := range locations { - keys = append(keys, key) - } - sort.Strings(keys) - - var builder strings.Builder - builder.WriteString("type Location struct {\n") - for _, name := range keys { - switch name { - case "shared": - builder.WriteString("\tShared bool `json:\"shared\"`\n") - case "from_panorama": - builder.WriteString("\tFromPanorama bool `json:\"from_panorama\"`\n") - case "vsys": - builder.WriteString("\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n") - case "device_group": - builder.WriteString("\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n") - } - } - builder.WriteString("}\n") - - nestedStructsDefinitionsForLocation(locations, "vsys", "VsysLocation", &builder) - nestedStructsDefinitionsForLocation(locations, "device_group", "DeviceGroupLocation", &builder) - - return builder.String(), nil -} - -func nestedStructsDefinitionsForLocation(locations map[string]*properties.Location, locationName string, structName string, builder *strings.Builder) { - if _, ok := locations[locationName]; ok { - keys := make([]string, 0, len(locations[locationName].Vars)) - for key := range locations[locationName].Vars { - keys = append(keys, key) - } - sort.Strings(keys) - - var namesOriginal, namesCamelCaseWithSpaces []string - for _, name := range keys { - namesOriginal = append(namesOriginal, name) - namesCamelCaseWithSpaces = append(namesCamelCaseWithSpaces, naming.CamelCase("", name, "", true)) - } - namesCamelCaseWithSpaces = MakeIndentationEqual(namesCamelCaseWithSpaces) - - builder.WriteString("\ntype " + structName + " struct {\n") - for idx, name := range namesCamelCaseWithSpaces { - builder.WriteString("\t" + name + " string `json:\"" + namesOriginal[idx] + "\"`\n") - } - builder.WriteString("}\n") - } -} From 5bc88c5ff1a19b7ba99435bda574fabbfdf427a2 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 13:34:52 +0100 Subject: [PATCH 13/32] tests for functions used while generating structs and funcs --- pkg/translate/funcs_test.go | 15 ++++ pkg/translate/names.go | 19 ------ pkg/translate/names_test.go | 12 ---- pkg/translate/structs.go | 4 +- pkg/translate/structs_test.go | 125 +++++++++------------------------- 5 files changed, 48 insertions(+), 127 deletions(-) diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index c2cb5d52..b6042a98 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -1 +1,16 @@ package translate + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAsEntryXpath(t *testing.T) { + // given + + // when + asEntryXpath := AsEntryXpath("device_group", "{{ Entry $panorama_device }}") + + // then + assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) +} diff --git a/pkg/translate/names.go b/pkg/translate/names.go index bd9133ee..3c62684a 100644 --- a/pkg/translate/names.go +++ b/pkg/translate/names.go @@ -1,7 +1,5 @@ package translate -import "strings" - // PackageName Get package name from Go SDK path func PackageName(list []string) string { if len(list) == 0 { @@ -9,20 +7,3 @@ func PackageName(list []string) string { } return list[len(list)-1] } - -// MakeIndentationEqual Check max lenght of the string in the list and then add spaces at the end of very name to make equal indentation -func MakeIndentationEqual(list []string) []string { - maxLength := 0 - - for _, str := range list { - if len(str) > maxLength { - maxLength = len(str) - } - } - - for idx, str := range list { - list[idx] = str + strings.Repeat(" ", maxLength-len(str)) - } - - return list -} diff --git a/pkg/translate/names_test.go b/pkg/translate/names_test.go index 0b4e5d6a..f0a7c2cc 100644 --- a/pkg/translate/names_test.go +++ b/pkg/translate/names_test.go @@ -15,15 +15,3 @@ func TestPackageName(t *testing.T) { // then assert.Equal(t, "address", packageName) } - -func TestMakeIndentationEqual(t *testing.T) { - // given - givenItems := []string{"test", "a"} - expectedItems := []string{"test", "a "} - - // when - changedItems := MakeIndentationEqual(givenItems) - - // then - assert.Equal(t, expectedItems, changedItems) -} diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index ce477977..88eb4fb0 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -5,7 +5,7 @@ import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) -func LocationType(name string, location properties.Location, pointer bool) string { +func LocationType(name string, location *properties.Location, pointer bool) string { prefix := "" if pointer { prefix = "*" @@ -17,7 +17,7 @@ func LocationType(name string, location properties.Location, pointer bool) strin } } -func OmitEmpty(location properties.Location) string { +func OmitEmpty(location *properties.Location) string { if location.Vars != nil { return ",omitempty" } else { diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 6a2f4071..4b7de4a4 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -6,9 +6,7 @@ import ( "testing" ) -func TestStructsDefinitionsForLocation(t *testing.T) { - // given - var sampleSpec = `name: 'Address' +const sampleSpec = `name: 'Address' terraform_provider_suffix: 'address' go_sdk_path: - 'objects' @@ -22,33 +20,6 @@ locations: panorama: true ngfw: true xpath: ['config', 'shared'] - 'from_panorama': - description: 'Located in the config pushed from Panorama.' - read_only: true - device: - ngfw: true - xpath: ['config', 'panorama'] - 'vsys': - description: 'Located in a specific vsys.' - device: - panorama: true - ngfw: true - xpath: - - 'config' - - 'devices' - - '{{ Entry $ngfw_device }}' - - 'vsys' - - '{{ Entry $vsys }}' - vars: - 'ngfw_device': - description: 'The NGFW device.' - default: 'localhost.localdomain' - 'vsys': - description: 'The vsys.' - default: 'vsys1' - validation: - not_values: - 'shared': 'The vsys cannot be "shared". Use the "shared" path instead.' 'device_group': description: 'Located in a specific device group.' device: @@ -76,74 +47,40 @@ entry: min: 1 max: 63 version: '10.1.0' -spec: - params: - description: - description: 'The description.' - type: 'string' - length: - min: 0 - max: 1023 - profiles: - - - xpath: ["description"] - tags: - description: 'The administrative tags.' - type: 'list' - count: - max: 64 - items: - type: 'string' - length: - max: 127 - profiles: - - - type: 'member' - xpath: ["tag"] - one_of: - 'ip_netmask': - description: 'The IP netmask value.' - profiles: - - - xpath: ["ip-netmask"] - 'ip_range': - description: 'The IP range value.' - profiles: - - - xpath: ["ip-range"] - 'fqdn': - description: 'The FQDN value.' - regex: '^[a-zA-Z0-9_]([a-zA-Z0-9:_-])+[a-zA-Z0-9]$' - length: - min: 1 - max: 255 - profiles: - - - xpath: ["fqdn"] - 'ip_wildcard': - description: 'The IP wildcard value.' - profiles: - - - xpath: ["ip-wildcard"] ` - var expectedStructsForLocation = "type Location struct {\n" + - "\tDeviceGroup *DeviceGroupLocation `json:\"device_group,omitempty\"`\n" + - "\tFromPanorama bool `json:\"from_panorama\"`\n" + - "\tShared bool `json:\"shared\"`\n" + - "\tVsys *VsysLocation `json:\"vsys,omitempty\"`\n" + - "}\n" + - "\ntype VsysLocation struct {\n" + - "\tNgfwDevice string `json:\"ngfw_device\"`\n" + - "\tVsys string `json:\"vsys\"`\n}\n" + - "\ntype DeviceGroupLocation struct {\n" + - "\tDeviceGroup string `json:\"device_group\"`\n" + - "\tPanoramaDevice string `json:\"panorama_device\"`\n" + - "}\n" + +func TestLocationType(t *testing.T) { + // given + yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec)) + locationKeys := []string{"device_group", "shared"} + locations := yamlParsedData.Locations + var locationTypes []string // when + for _, locationKey := range locationKeys { + locationTypes = append(locationTypes, LocationType(locationKey, locations[locationKey], true)) + } + + // then + assert.NotEmpty(t, locationTypes) + assert.Contains(t, locationTypes, "*DeviceGroupLocation") + assert.Contains(t, locationTypes, "bool") +} + +func TestOmitEmpty(t *testing.T) { + // given yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec)) - structsForLocation, _ := StructsDefinitionsForLocation(yamlParsedData.Locations) + locationKeys := []string{"device_group", "shared"} + locations := yamlParsedData.Locations + var omitEmptyLocations []string + + // when + for _, locationKey := range locationKeys { + omitEmptyLocations = append(omitEmptyLocations, OmitEmpty(locations[locationKey])) + } // then - assert.Equal(t, expectedStructsForLocation, structsForLocation) + assert.NotEmpty(t, omitEmptyLocations) + assert.Contains(t, omitEmptyLocations, ",omitempty") + assert.Contains(t, omitEmptyLocations, "") } From d9fdd835cea6de557218bcd258a7ae6ffde22240 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 13:47:12 +0100 Subject: [PATCH 14/32] fix issues after tests --- specs/panorama/device-group.yaml | 10 ++++++++++ specs/panorama/template-stack.yaml | 10 ++++++++++ specs/panorama/template.yaml | 10 ++++++++++ specs/policies/security-policy-rule.yaml | 2 +- templates/sdk/location.tmpl | 4 +--- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/specs/panorama/device-group.yaml b/specs/panorama/device-group.yaml index cb3defc6..9fd5b545 100644 --- a/specs/panorama/device-group.yaml +++ b/specs/panorama/device-group.yaml @@ -16,6 +16,16 @@ locations: - '{{ Entry $ngfw_device }}' - 'vsys' - '{{ Entry $vsys }}' + vars: + 'ngfw_device': + description: 'The NGFW device.' + default: 'localhost.localdomain' + 'vsys': + description: 'The vsys.' + default: 'vsys1' + validation: + not_values: + 'shared': 'The vsys cannot be "shared". Use the "shared" path instead.' entry: name: description: 'The name of the device group.' diff --git a/specs/panorama/template-stack.yaml b/specs/panorama/template-stack.yaml index 30eb3c3f..680a1e5d 100644 --- a/specs/panorama/template-stack.yaml +++ b/specs/panorama/template-stack.yaml @@ -16,6 +16,16 @@ locations: - '{{ Entry $ngfw_device }}' - 'vsys' - '{{ Entry $vsys }}' + vars: + 'ngfw_device': + description: 'The NGFW device.' + default: 'localhost.localdomain' + 'vsys': + description: 'The vsys.' + default: 'vsys1' + validation: + not_values: + 'shared': 'The vsys cannot be "shared". Use the "shared" path instead.' entry: name: description: 'The name of the template stack.' diff --git a/specs/panorama/template.yaml b/specs/panorama/template.yaml index cb2b31aa..6183a30c 100644 --- a/specs/panorama/template.yaml +++ b/specs/panorama/template.yaml @@ -16,6 +16,16 @@ locations: - '{{ Entry $ngfw_device }}' - 'vsys' - '{{ Entry $vsys }}' + vars: + 'ngfw_device': + description: 'The NGFW device.' + default: 'localhost.localdomain' + 'vsys': + description: 'The vsys.' + default: 'vsys1' + validation: + not_values: + 'shared': 'The vsys cannot be "shared". Use the "shared" path instead.' entry: name: description: 'The name of the template.' diff --git a/specs/policies/security-policy-rule.yaml b/specs/policies/security-policy-rule.yaml index 7319b3dd..e7bb344a 100644 --- a/specs/policies/security-policy-rule.yaml +++ b/specs/policies/security-policy-rule.yaml @@ -2,7 +2,7 @@ name: 'Security policy rule' terraform_provider_suffix: 'security_policy_rule' go_sdk_path: - 'policies' - - 'security-policy-rule' + - 'security_policy_rule' xpath_suffix: - 'security' - 'rules' diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index b229a1c2..a0379982 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -20,10 +20,8 @@ type {{locationType $key $location false}} struct { {{camelCase $key}} string `json:"{{$key}}"` {{- end}} } +{{end}} {{- end}} -{{- end}} - - func (o Location) IsValid() error { count := 0 From f1f2525c25bd436728e9dc9cc3bc86e172155e2f Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 5 Mar 2024 14:20:49 +0100 Subject: [PATCH 15/32] remove not required spaces from templates --- templates/sdk/entry.tmpl | 2 +- templates/sdk/interfaces.tmpl | 2 +- templates/sdk/location.tmpl | 4 ++-- templates/sdk/service.tmpl | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 7f410c4c..3b3d1518 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -1 +1 @@ -package {{ packageName .GoSdkPath }} \ No newline at end of file +package {{packageName .GoSdkPath}} \ No newline at end of file diff --git a/templates/sdk/interfaces.tmpl b/templates/sdk/interfaces.tmpl index 00a72dcc..c886baa1 100644 --- a/templates/sdk/interfaces.tmpl +++ b/templates/sdk/interfaces.tmpl @@ -1,4 +1,4 @@ -package {{ packageName .GoSdkPath }} +package {{packageName .GoSdkPath}} type Specifier func(Entry) (any, error) diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index a0379982..c5b01387 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -1,4 +1,4 @@ -package {{packageName .GoSdkPath }} +package {{packageName .GoSdkPath}} import ( "fmt" @@ -63,7 +63,7 @@ func (o Location) Xpath(vn version.Number, name string) ([]string, error) { ans = []string{ {{- range $name, $xpath := $location.Xpath}} {{- if contains $xpath "Entry"}} - {{ asEntryXpath $key $xpath }} + {{asEntryXpath $key $xpath}} {{- else}} "{{$xpath}}", {{- end}} diff --git a/templates/sdk/service.tmpl b/templates/sdk/service.tmpl index 7f410c4c..3b3d1518 100644 --- a/templates/sdk/service.tmpl +++ b/templates/sdk/service.tmpl @@ -1 +1 @@ -package {{ packageName .GoSdkPath }} \ No newline at end of file +package {{packageName .GoSdkPath}} \ No newline at end of file From b2fb71462a3623317e1ea48d056c6064358e0542 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 09:23:23 +0100 Subject: [PATCH 16/32] introduce new struct NameVariant for Location names and variables, get rid of camelCase function from template --- pkg/generate/generator.go | 6 +--- pkg/properties/normalized.go | 47 +++++++++++++++++++++++++++++-- pkg/properties/normalized_test.go | 24 ++++++++++++++++ pkg/translate/funcs.go | 1 - pkg/translate/funcs_test.go | 2 +- pkg/translate/structs.go | 5 ++-- pkg/translate/structs_test.go | 2 +- templates/sdk/location.tmpl | 20 ++++++------- 8 files changed, 83 insertions(+), 24 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 0e433122..9d923931 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -2,7 +2,6 @@ package generate 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/translate" "io" @@ -76,10 +75,7 @@ func (c *Creator) generateOutputFileFromTemplate(tmpl *template.Template, output func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := fmt.Sprintf("%s/%s", c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, - "camelCase": func(name string) string { - return naming.CamelCase("", name, "", true) - }, + "packageName": translate.PackageName, "locationType": translate.LocationType, "omitEmpty": translate.OmitEmpty, "contains": func(full, part string) bool { diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 2d99d67e..936fceeb 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/content" + "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "io/fs" "path/filepath" "runtime" @@ -21,7 +22,13 @@ type Normalization struct { Spec *Spec `json:"spec" yaml:"spec"` } +type NameVariant struct { + Underscore string + CamelCase string +} + type Location struct { + Name *NameVariant Description string `json:"description" yaml:"description"` Device *LocationDevice `json:"device" yaml:"device"` Xpath []string `json:"xpath" yaml:"xpath"` @@ -35,6 +42,7 @@ type LocationDevice struct { } type LocationVar struct { + Name *NameVariant Description string `json:"description" yaml:"description"` Required bool `json:"required" yaml:"required"` Validation *LocationVarValidation `json:"validation" yaml:"validation"` @@ -128,9 +136,31 @@ func GetNormalizations() ([]string, error) { } func ParseSpec(input []byte) (*Normalization, error) { - var ans Normalization - err := content.Unmarshal(input, &ans) - return &ans, err + var spec Normalization + + err := content.Unmarshal(input, &spec) + + err = spec.AddNameVariants() + + return &spec, err +} + +func (spec *Normalization) AddNameVariants() error { + for key, location := range spec.Locations { + location.Name = &NameVariant{ + Underscore: key, + CamelCase: naming.CamelCase("", key, "", true), + } + + for subkey, variable := range location.Vars { + variable.Name = &NameVariant{ + Underscore: subkey, + CamelCase: naming.CamelCase("", subkey, "", true), + } + } + } + + return nil } func (spec *Normalization) Sanity() error { @@ -167,3 +197,14 @@ func (spec *Normalization) Validate() []error { return checks } + +//func (spec *Normalization) GetCamelCaseLocations() map[string]*Location { +// locations := map[string]*Location{} +// +// for key, location := range spec.Locations { +// camelCaseKey := naming.CamelCase("", key, "", true) +// locations[camelCaseKey] = location +// } +// +// return locations +//} diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index b6a01fb3..7d3a8b72 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -154,6 +154,9 @@ xpath_suffix: - address locations: device_group: + name: + underscore: device_group + camelcase: DeviceGroup description: Located in a specific device group. device: panorama: true @@ -167,16 +170,25 @@ locations: read_only: false vars: device_group: + name: + underscore: device_group + camelcase: DeviceGroup description: The device group. required: true validation: not_values: shared: The device group cannot be "shared". Use the "shared" path instead. panorama_device: + name: + underscore: panorama_device + camelcase: PanoramaDevice description: The panorama device. required: false validation: null from_panorama: + name: + underscore: from_panorama + camelcase: FromPanorama description: Located in the config pushed from Panorama. device: panorama: false @@ -187,6 +199,9 @@ locations: read_only: true vars: {} shared: + name: + underscore: shared + camelcase: Shared description: Located in shared. device: panorama: true @@ -197,6 +212,9 @@ locations: read_only: false vars: {} vsys: + name: + underscore: vsys + camelcase: Vsys description: Located in a specific vsys. device: panorama: true @@ -210,10 +228,16 @@ locations: read_only: false vars: ngfw_device: + name: + underscore: ngfw_device + camelcase: NgfwDevice description: The NGFW device. required: false validation: null vsys: + name: + underscore: vsys + camelcase: Vsys description: The vsys. required: false validation: diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 9f507c2c..10b04a4a 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -6,7 +6,6 @@ import ( ) func AsEntryXpath(location, xpath string) string { - location = naming.CamelCase("", location, "", true) xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) xpath = naming.CamelCase("", xpath, "", true) return "util.AsEntryXpath([]string{o." + location + "." + xpath + "})," diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index b6042a98..a47ede8a 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -9,7 +9,7 @@ func TestAsEntryXpath(t *testing.T) { // given // when - asEntryXpath := AsEntryXpath("device_group", "{{ Entry $panorama_device }}") + asEntryXpath := AsEntryXpath("DeviceGroup", "{{ Entry $panorama_device }}") // then assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 88eb4fb0..33e21fb4 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -1,17 +1,16 @@ package translate import ( - "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) -func LocationType(name string, location *properties.Location, pointer bool) string { +func LocationType(location *properties.Location, pointer bool) string { prefix := "" if pointer { prefix = "*" } if location.Vars != nil { - return prefix + naming.CamelCase("", name, "", true) + "Location" + return prefix + location.Name.CamelCase + "Location" } else { return "bool" } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 4b7de4a4..6bd61b31 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -58,7 +58,7 @@ func TestLocationType(t *testing.T) { // when for _, locationKey := range locationKeys { - locationTypes = append(locationTypes, LocationType(locationKey, locations[locationKey], true)) + locationTypes = append(locationTypes, LocationType(locations[locationKey], true)) } // then diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index c5b01387..cdc57075 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -10,14 +10,14 @@ import ( type Location struct { {{range $key, $location := .Locations}} - {{- camelCase $key}} {{locationType $key $location true}} `json:"{{$key}}{{omitEmpty $location}}"` + {{- $location.Name.CamelCase }} {{locationType $location true}} `json:"{{$location.Name.Underscore}}{{omitEmpty $location}}"` {{end}} } {{range $key, $location := .Locations}} {{- if $location.Vars}} -type {{locationType $key $location false}} struct { +type {{locationType $location false}} struct { {{- range $key, $var := $location.Vars}} - {{camelCase $key}} string `json:"{{$key}}"` + {{$var.Name.CamelCase}} string `json:"{{$var.Name.Underscore}}"` {{- end}} } {{end}} @@ -27,10 +27,10 @@ func (o Location) IsValid() error { switch { {{- range $key, $location := .Locations}} - case o.{{- camelCase $key}}{{if ne (locationType $key $location true) "bool"}} != nil{{end}}: + case o.{{- $location.Name.CamelCase}}{{if ne (locationType $location true) "bool"}} != nil{{end}}: {{- range $name, $var := $location.Vars}} - if o.{{camelCase $key}}.{{camelCase $name}} == "" { - return fmt.Errorf("{{camelCase $name}} is unspecified") + if o.{{$location.Name.CamelCase}}.{{$var.Name.CamelCase}} == "" { + return fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") } {{- end}} count++ @@ -54,16 +54,16 @@ func (o Location) Xpath(vn version.Number, name string) ([]string, error) { switch { {{- range $key, $location := .Locations}} - case o.{{- camelCase $key}}{{if ne (locationType $key $location true) "bool"}} != nil{{end}}: + case o.{{- $location.Name.CamelCase}}{{if ne (locationType $location true) "bool"}} != nil{{end}}: {{- range $name, $var := $location.Vars}} - if o.{{camelCase $key}}.{{camelCase $name}} == "" { - return nil, fmt.Errorf("{{camelCase $name}} is unspecified") + if o.{{$location.Name.CamelCase}}.{{$var.Name.CamelCase}} == "" { + return nil, fmt.Errorf("{{$var.Name.CamelCase}} is unspecified") } {{- end}} ans = []string{ {{- range $name, $xpath := $location.Xpath}} {{- if contains $xpath "Entry"}} - {{asEntryXpath $key $xpath}} + {{asEntryXpath $location.Name.CamelCase $xpath}} {{- else}} "{{$xpath}}", {{- end}} From b535826b5ea0bcd5956088016e6c49c1c1a371ff Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 09:50:07 +0100 Subject: [PATCH 17/32] Change Go SDK path for security policy rule --- specs/policies/security-policy-rule.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/policies/security-policy-rule.yaml b/specs/policies/security-policy-rule.yaml index e7bb344a..978fa99d 100644 --- a/specs/policies/security-policy-rule.yaml +++ b/specs/policies/security-policy-rule.yaml @@ -2,7 +2,8 @@ name: 'Security policy rule' terraform_provider_suffix: 'security_policy_rule' go_sdk_path: - 'policies' - - 'security_policy_rule' + - 'rules' + - 'security' xpath_suffix: - 'security' - 'rules' From ea21716993be7864be66efe61aec8c004f5afa2c Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 12:53:19 +0100 Subject: [PATCH 18/32] extend configuration file with settings for copying static assets --- cmd/mktp/config.yaml | 9 ++++++++- pkg/properties/config.go | 14 +++++++++++++- pkg/properties/config_test.go | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/cmd/mktp/config.yaml b/cmd/mktp/config.yaml index 0332d3a8..b2b20da3 100644 --- a/cmd/mktp/config.yaml +++ b/cmd/mktp/config.yaml @@ -1,3 +1,10 @@ output: go_sdk: "../generated/pango" - terraform_provider: "../generated/terraform-provider-panos" \ No newline at end of file + terraform_provider: "../generated/terraform-provider-panos" +assets: + util_package: + source: "assets/util" + target: + go_sdk: true + terraform_provider: false + destination: "util" diff --git a/pkg/properties/config.go b/pkg/properties/config.go index 7846d1cf..1e5e3648 100644 --- a/pkg/properties/config.go +++ b/pkg/properties/config.go @@ -3,7 +3,8 @@ package properties import "github.com/paloaltonetworks/pan-os-codegen/pkg/content" type Config struct { - Output OutputPaths `json:"output" yaml:"output"` + Output OutputPaths `json:"output" yaml:"output"` + Assets map[string]*Asset `json:"assets" yaml:"assets"` } type OutputPaths struct { @@ -11,6 +12,17 @@ type OutputPaths struct { TerraformProvider string `json:"terraform_provider" yaml:"terraform_provider"` } +type Asset struct { + Source string `json:"source" yaml:"source"` + Target *Target `json:"target" yaml:"target"` + Destination string `json:"destination" yaml:"destination"` +} + +type Target struct { + GoSdk bool `json:"go_sdk" yaml:"go_sdk"` + TerraformProvider bool `json:"terraform_provider" yaml:"terraform_provider"` +} + func ParseConfig(input []byte) (*Config, error) { var ans Config err := content.Unmarshal(input, &ans) diff --git a/pkg/properties/config_test.go b/pkg/properties/config_test.go index 51f7a1fd..f08e45fe 100644 --- a/pkg/properties/config_test.go +++ b/pkg/properties/config_test.go @@ -10,6 +10,13 @@ func TestConfig(t *testing.T) { const content = `output: go_sdk: "../generated/pango" terraform_provider: "../generated/terraform-provider-panos" +assets: + util_package: + source: "assets/util" + target: + go_sdk: true + terraform_provider: false + destination: "util" ` // when @@ -20,4 +27,11 @@ func TestConfig(t *testing.T) { assert.NotEmptyf(t, config.Output, "Config output cannot be empty") assert.NotEmptyf(t, config.Output.GoSdk, "Config Go SDK path cannot be empty") assert.NotEmptyf(t, config.Output.TerraformProvider, "Config Terraform provider path cannot be empty") + assert.NotEmpty(t, config.Assets) + assert.Equal(t, 1, len(config.Assets)) + assert.Equal(t, 1, len(config.Assets)) + assert.Equal(t, "assets/util", config.Assets["util_package"].Source) + assert.True(t, config.Assets["util_package"].Target.GoSdk) + assert.False(t, config.Assets["util_package"].Target.TerraformProvider) + assert.Equal(t, "util", config.Assets["util_package"].Destination) } From d2424f3fecb27438a9cf532e16597605f5b009ac Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 12:53:36 +0100 Subject: [PATCH 19/32] static files from pango in develop branch --- assets/errors/panos.go | 140 ++++++++++++++++ assets/errors/panos_test.go | 73 +++++++++ assets/util/actioner.go | 5 + assets/util/bulk_element.go | 19 +++ assets/util/comparison.go | 76 +++++++++ assets/util/const.go | 39 +++++ assets/util/copy.go | 31 ++++ assets/util/elementer.go | 6 + assets/util/entry.go | 67 ++++++++ assets/util/hitcount.go | 67 ++++++++ assets/util/jobs.go | 71 +++++++++ assets/util/license.go | 17 ++ assets/util/lock.go | 15 ++ assets/util/member.go | 68 ++++++++ assets/util/pangoclient.go | 21 +++ assets/util/pangocommand.go | 9 ++ assets/util/retriever.go | 5 + assets/util/util.go | 282 +++++++++++++++++++++++++++++++++ assets/util/util_test.go | 224 ++++++++++++++++++++++++++ assets/util/xapiclient.go | 52 ++++++ assets/util/xmlnode.go | 54 +++++++ assets/version/version.go | 72 +++++++++ assets/version/version_test.go | 124 +++++++++++++++ 23 files changed, 1537 insertions(+) create mode 100644 assets/errors/panos.go create mode 100644 assets/errors/panos_test.go create mode 100644 assets/util/actioner.go create mode 100644 assets/util/bulk_element.go create mode 100644 assets/util/comparison.go create mode 100644 assets/util/const.go create mode 100644 assets/util/copy.go create mode 100644 assets/util/elementer.go create mode 100644 assets/util/entry.go create mode 100644 assets/util/hitcount.go create mode 100644 assets/util/jobs.go create mode 100644 assets/util/license.go create mode 100644 assets/util/lock.go create mode 100644 assets/util/member.go create mode 100644 assets/util/pangoclient.go create mode 100644 assets/util/pangocommand.go create mode 100644 assets/util/retriever.go create mode 100644 assets/util/util.go create mode 100644 assets/util/util_test.go create mode 100644 assets/util/xapiclient.go create mode 100644 assets/util/xmlnode.go create mode 100644 assets/version/version.go create mode 100644 assets/version/version_test.go diff --git a/assets/errors/panos.go b/assets/errors/panos.go new file mode 100644 index 00000000..b56e8c49 --- /dev/null +++ b/assets/errors/panos.go @@ -0,0 +1,140 @@ +package errors + +import ( + "encoding/xml" + stderr "errors" + "fmt" + "strings" + + "github.com/PaloAltoNetworks/pango/util" +) + +var InvalidFilterError = stderr.New("filter is improperly formatted") +var NameNotSpecifiedError = stderr.New("name is not specified") +var NoLocationSpecifiedError = stderr.New("no location specified") +var UnrecognizedOperatorError = stderr.New("unsupported filter operator") +var UnsupportedFilterTypeError = stderr.New("unsupported type for filtering") + +// Panos is an error returned from PAN-OS. +// +// The error contains both the error message and the code returned from PAN-OS. +type Panos struct { + Msg string + Code int +} + +// Error returns the error message. +func (e Panos) Error() string { + return e.Msg +} + +// ObjectNotFound returns true if this is an object not found error. +func (e Panos) ObjectNotFound() bool { + return e.Code == 7 +} + +// ObjectNotFound returns an object not found error. +func ObjectNotFound() Panos { + return Panos{ + Msg: "Object not found", + Code: 7, + } +} + +// Parse attempts to parse an error from the given XML response. +func Parse(body []byte) error { + var e errorCheck + + _ = xml.Unmarshal(body, &e) + if e.Failed() { + return Panos{ + Msg: e.Message(), + Code: e.Code, + } + } + + return nil +} + +type errorCheck struct { + XMLName xml.Name `xml:"response"` + Status string `xml:"status,attr"` + Code int `xml:"code,attr"` + Msg *errorCheckMsg `xml:"msg"` + ResultMsg *string `xml:"result>msg"` +} + +type errorCheckMsg struct { + Line []util.CdataText `xml:"line"` + Message string `xml:",chardata"` +} + +func (e *errorCheck) Failed() bool { + if e.Status == "failed" || e.Status == "error" { + return true + } else if e.Code == 0 || e.Code == 19 || e.Code == 20 { + return false + } + + return true +} + +func (e *errorCheck) Message() string { + if e.Msg != nil { + if len(e.Msg.Line) > 0 { + var b strings.Builder + for i := range e.Msg.Line { + if i != 0 { + b.WriteString(" | ") + } + b.WriteString(strings.TrimSpace(e.Msg.Line[i].Text)) + } + return b.String() + } + + if e.Msg.Message != "" { + return e.Msg.Message + } + } + + if e.ResultMsg != nil { + return *e.ResultMsg + } + + return e.CodeError() +} + +func (e *errorCheck) CodeError() string { + switch e.Code { + case 1: + return "Unknown command" + case 2, 3, 4, 5, 11: + return fmt.Sprintf("Internal error (%d) encountered", e.Code) + case 6: + return "Bad Xpath" + case 7: + return "Object not found" + case 8: + return "Object not unique" + case 10: + return "Reference count not zero" + case 12: + return "Invalid object" + case 14: + return "Operation not possible" + case 15: + return "Operation denied" + case 16: + return "Unauthorized" + case 17: + return "Invalid command" + case 18: + return "Malformed command" + case 0, 19, 20: + return "" + case 22: + return "Session timed out" + default: + return fmt.Sprintf("(%d) Unknown failure code, operation failed", e.Code) + } +} diff --git a/assets/errors/panos_test.go b/assets/errors/panos_test.go new file mode 100644 index 00000000..70f07dcf --- /dev/null +++ b/assets/errors/panos_test.go @@ -0,0 +1,73 @@ +package errors + +import ( + "fmt" + "strings" + "testing" +) + +func TestGetSingularMissingObjectIsError(t *testing.T) { + data := `` + + err := Parse([]byte(data)) + if err == nil { + t.Errorf("Error is nil") + } else { + e2, ok := err.(Panos) + if !ok { + t.Errorf("Not a panos error") + } else if !e2.ObjectNotFound() { + t.Errorf("Not an object not found error") + } + } +} + +func TestShowSingularMissingObjectIsError(t *testing.T) { + data := `No such node` + + err := Parse([]byte(data)) + if err == nil { + t.Errorf("Error is nil") + } else { + e2, ok := err.(Panos) + if !ok { + t.Errorf("Not a panos error") + } else if e2.Msg != "No such node" { + t.Errorf("Incorrect msg: %s", e2.Msg) + } + } +} + +func TestMultilineErrorMessage(t *testing.T) { + expected := "HTTP method must be GET" + data := fmt.Sprintf(` server -> first server is invalid. %s, Username/Password must not be empty when Tag Distribution is chosen]]> server is invalid]]>`, expected) + + err := Parse([]byte(data)) + if err == nil { + t.Errorf("Error is nil") + } else { + e2, ok := err.(Panos) + if !ok { + t.Errorf("Not a panos error") + } else if !strings.Contains(e2.Msg, expected) { + t.Errorf("Does not contain the expected substring: %s", e2.Msg) + } + } +} + +func TestFailedExportErrorMessage(t *testing.T) { + expected := `Parameter "format" is required while exporting certificate` + data := `Parameter "format" is required while exporting certificate` + + err := Parse([]byte(data)) + if err == nil { + t.Errorf("Error is nil") + } else { + e2, ok := err.(Panos) + if !ok { + t.Errorf("Not a pnos error") + } else if !strings.Contains(e2.Msg, expected) { + t.Errorf("Does not contain the expected substring: %s", e2.Msg) + } + } +} diff --git a/assets/util/actioner.go b/assets/util/actioner.go new file mode 100644 index 00000000..0b516835 --- /dev/null +++ b/assets/util/actioner.go @@ -0,0 +1,5 @@ +package util + +type Actioner interface { + Action() string +} diff --git a/assets/util/bulk_element.go b/assets/util/bulk_element.go new file mode 100644 index 00000000..5a129757 --- /dev/null +++ b/assets/util/bulk_element.go @@ -0,0 +1,19 @@ +package util + +import ( + "encoding/xml" +) + +// BulkElement is a generic bulk container for bulk operations. +type BulkElement struct { + XMLName xml.Name + Data []interface{} +} + +// Config returns an interface to be Marshaled. +func (o BulkElement) Config() interface{} { + if len(o.Data) == 1 { + return o.Data[0] + } + return o +} diff --git a/assets/util/comparison.go b/assets/util/comparison.go new file mode 100644 index 00000000..e5c164b7 --- /dev/null +++ b/assets/util/comparison.go @@ -0,0 +1,76 @@ +package util + +func UnorderedListsMatch(a, b []string) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } else if len(a) != len(b) { + return false + } + + for _, x := range a { + var found bool + for _, y := range b { + if x == y { + found = true + break + } + } + if !found { + return false + } + } + + return true +} + +func OrderedListsMatch(a, b []string) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } else if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} + +func TargetsMatch(a, b map[string][]string) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } else if len(a) != len(b) { + return false + } + + for key := range a { + if !UnorderedListsMatch(a[key], b[key]) { + return false + } + } + + return true +} + +func OptionalStringsMatch(a, b *string) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } + + return *a == *b +} + +func StringsMatch(a, b string) bool { + return a == b +} diff --git a/assets/util/const.go b/assets/util/const.go new file mode 100644 index 00000000..c5a6a729 --- /dev/null +++ b/assets/util/const.go @@ -0,0 +1,39 @@ +package util + +// Rulebase constants for various policies. +const ( + Rulebase = "rulebase" + PreRulebase = "pre-rulebase" + PostRulebase = "post-rulebase" +) + +// Valid values to use for VsysImport() or VsysUnimport(). +const ( + InterfaceImport = "interface" + VirtualRouterImport = "virtual-router" + VirtualWireImport = "virtual-wire" + VlanImport = "vlan" +) + +// These constants are valid move locations to pass to various movement +// functions (aka - policy management). +const ( + MoveSkip = iota + MoveBefore + MoveDirectlyBefore + MoveAfter + MoveDirectlyAfter + MoveTop + MoveBottom +) + +// Valid values to use for any function expecting a pango query type `qt`. +const ( + Get = "get" + Show = "show" +) + +// PanosTimeWithoutTimezoneFormat is a time (missing the timezone) that PAN-OS +// will give sometimes. Combining this with `Clock()` to get a usable time. +// report that does not contain +const PanosTimeWithoutTimezoneFormat = "2006/01/02 15:04:05" diff --git a/assets/util/copy.go b/assets/util/copy.go new file mode 100644 index 00000000..6a7765cd --- /dev/null +++ b/assets/util/copy.go @@ -0,0 +1,31 @@ +package util + +func CopyStringSlice(v []string) []string { + if v == nil { + return nil + } + + ans := make([]string, len(v)) + copy(ans, v) + + return ans +} + +func CopyTargets(v map[string][]string) map[string][]string { + if v == nil { + return nil + } + + ans := make(map[string][]string) + for key, oval := range v { + if oval == nil { + ans[key] = nil + } else { + val := make([]string, len(oval)) + copy(val, oval) + ans[key] = val + } + } + + return ans +} diff --git a/assets/util/elementer.go b/assets/util/elementer.go new file mode 100644 index 00000000..72688d67 --- /dev/null +++ b/assets/util/elementer.go @@ -0,0 +1,6 @@ +package util + +// Elementer is an interface for commits. +type Elementer interface { + Element() interface{} +} diff --git a/assets/util/entry.go b/assets/util/entry.go new file mode 100644 index 00000000..0c1ea4c9 --- /dev/null +++ b/assets/util/entry.go @@ -0,0 +1,67 @@ +package util + +import ( + "encoding/xml" +) + +// EntryType defines an entry config node used for sending and receiving XML +// from PAN-OS. +type EntryType struct { + Entries []Entry `xml:"entry"` +} + +// Entry is a standalone entry struct. +type Entry struct { + XMLName xml.Name `xml:"entry"` + Value string `xml:"name,attr"` +} + +// EntToStr normalizes an EntryType pointer into a list of strings. +func EntToStr(e *EntryType) []string { + if e == nil { + return nil + } + + ans := make([]string, len(e.Entries)) + for i := range e.Entries { + ans[i] = e.Entries[i].Value + } + + return ans +} + +// StrToEnt converts a list of strings into an EntryType pointer. +func StrToEnt(e []string) *EntryType { + if e == nil { + return nil + } + + ans := make([]Entry, len(e)) + for i := range e { + ans[i] = Entry{Value: e[i]} + } + + return &EntryType{ans} +} + +// EntToOneStr normalizes an EntryType pointer for a max_items=1 XML node +// into a string. +func EntToOneStr(e *EntryType) string { + if e == nil || len(e.Entries) == 0 { + return "" + } + + return e.Entries[0].Value +} + +// OneStrToEnt converts a string into an EntryType pointer for a max_items=1 +// XML node. +func OneStrToEnt(e string) *EntryType { + if e == "" { + return nil + } + + return &EntryType{[]Entry{ + {Value: e}, + }} +} diff --git a/assets/util/hitcount.go b/assets/util/hitcount.go new file mode 100644 index 00000000..d10e7f77 --- /dev/null +++ b/assets/util/hitcount.go @@ -0,0 +1,67 @@ +package util + +import ( + "encoding/xml" +) + +// NewHitCountRequest returns a new hit count request struct. +// +// If the rules param is nil, then the hit count for all rules is returned. +func NewHitCountRequest(rulebase, vsys string, rules []string) interface{} { + req := hcReq{ + Vsys: hcReqVsys{ + Name: vsys, + Rulebase: hcReqRulebase{ + Name: rulebase, + Rules: hcReqRules{ + List: StrToMem(rules), + }, + }, + }, + } + + if req.Vsys.Rulebase.Rules.List == nil { + s := "" + req.Vsys.Rulebase.Rules.All = &s + } + + return req +} + +// HitCountResponse is the hit count response struct. +type HitCountResponse struct { + XMLName xml.Name `xml:"response"` + Results []HitCount `xml:"result>rule-hit-count>vsys>entry>rule-base>entry>rules>entry"` +} + +// HitCount is the hit count data for a specific rule. +type HitCount struct { + Name string `xml:"name,attr"` + Latest string `xml:"latest"` + HitCount uint `xml:"hit-count"` + LastHitTimestamp int `xml:"last-hit-timestamp"` + LastResetTimestamp int `xml:"last-reset-timestamp"` + FirstHitTimestamp int `xml:"first-hit-timestamp"` + RuleCreationTimestamp int `xml:"rule-creation-timestamp"` + RuleModificationTimestamp int `xml:"rule-modification-timestamp"` +} + +type hcReq struct { + XMLName xml.Name `xml:"show"` + Vsys hcReqVsys `xml:"rule-hit-count>vsys>vsys-name>entry"` +} + +type hcReqVsys struct { + Name string `xml:"name,attr"` + Rulebase hcReqRulebase `xml:"rule-base>entry"` +} + +type hcReqRulebase struct { + Name string `xml:"name,attr"` + Rules hcReqRules `xml:"rules"` +} + +type hcReqRules struct { + All *string `xml:"all"` + List *MemberType `xml:"list"` +} diff --git a/assets/util/jobs.go b/assets/util/jobs.go new file mode 100644 index 00000000..e3b9ca63 --- /dev/null +++ b/assets/util/jobs.go @@ -0,0 +1,71 @@ +package util + +import ( + "encoding/xml" + "strconv" + "strings" +) + +// JobResponse parses a XML response that includes a job ID. +type JobResponse struct { + XMLName xml.Name `xml:"response"` + Id uint `xml:"result>job"` +} + +// BasicJob is a struct for parsing minimal information about a submitted +// job to PANOS. +type BasicJob struct { + XMLName xml.Name `xml:"response"` + Result string `xml:"result>job>result"` + Progress uint `xml:"-"` + Details BasicJobDetails `xml:"result>job>details"` + Devices []devJob `xml:"result>job>devices>entry"` + Status string `xml:"result>job>status"` // For log retrieval jobs. + ProgressRaw string `xml:"result>job>progress"` +} + +func (o *BasicJob) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type localBasicJob BasicJob + var ans localBasicJob + if err := d.DecodeElement(&ans, &start); err != nil { + return err + } + + val, err := strconv.ParseUint(strings.TrimSpace(ans.ProgressRaw), 10, 32) + if err == nil { + ans.Progress = uint(val) + } + + *o = BasicJob(ans) + return nil +} + +type BasicJobDetails struct { + Lines []LineOrCdata `xml:"line"` +} + +func (o *BasicJobDetails) String() string { + ans := make([]string, 0, len(o.Lines)) + + for _, line := range o.Lines { + if line.Cdata != nil { + ans = append(ans, strings.TrimSpace(*line.Cdata)) + } else if line.Text != nil { + ans = append(ans, *line.Text) + } else { + ans = append(ans, "huh") + } + } + + return strings.Join(ans, " | ") +} + +type LineOrCdata struct { + Cdata *string `xml:",cdata"` + Text *string `xml:",chardata"` +} + +type devJob struct { + Serial string `xml:"serial-no"` + Result string `xml:"result"` +} diff --git a/assets/util/license.go b/assets/util/license.go new file mode 100644 index 00000000..5bd7f44f --- /dev/null +++ b/assets/util/license.go @@ -0,0 +1,17 @@ +package util + +import ( + "encoding/xml" +) + +// License defines a license entry. +type License struct { + XMLName xml.Name `xml:"entry"` + Feature string `xml:"feature"` + Description string `xml:"description"` + Serial string `xml:"serial"` + Issued string `xml:"issued"` + Expires string `xml:"expires"` + Expired string `xml:"expired"` + AuthCode string `xml:"authcode"` +} diff --git a/assets/util/lock.go b/assets/util/lock.go new file mode 100644 index 00000000..1e819c2f --- /dev/null +++ b/assets/util/lock.go @@ -0,0 +1,15 @@ +package util + +import ( + "encoding/xml" +) + +// Lock represents either a config lock or a commit lock. +type Lock struct { + XMLName xml.Name `xml:"entry"` + Owner string `xml:"name,attr"` + Name string `xml:"name"` + Type string `xml:"type"` + LoggedIn string `xml:"loggedin"` + Comment CdataText `xml:"comment"` +} diff --git a/assets/util/member.go b/assets/util/member.go new file mode 100644 index 00000000..658cbb0c --- /dev/null +++ b/assets/util/member.go @@ -0,0 +1,68 @@ +package util + +import ( + "encoding/xml" +) + +// MemberType defines a member config node used for sending and receiving XML +// from PAN-OS. +type MemberType struct { + Members []Member `xml:"member"` +} + +// Member defines a member config node used for sending and receiving XML +// from PANOS. +type Member struct { + XMLName xml.Name `xml:"member"` + Value string `xml:",chardata"` +} + +// MemToStr normalizes a MemberType pointer into a list of strings. +func MemToStr(e *MemberType) []string { + if e == nil { + return nil + } + + ans := make([]string, len(e.Members)) + for i := range e.Members { + ans[i] = e.Members[i].Value + } + + return ans +} + +// StrToMem converts a list of strings into a MemberType pointer. +func StrToMem(e []string) *MemberType { + if e == nil { + return nil + } + + ans := make([]Member, len(e)) + for i := range e { + ans[i] = Member{Value: e[i]} + } + + return &MemberType{ans} +} + +// MemToOneStr normalizes a MemberType pointer for a max_items=1 XML node +// into a string. +func MemToOneStr(e *MemberType) string { + if e == nil || len(e.Members) == 0 { + return "" + } + + return e.Members[0].Value +} + +// OneStrToMem converts a string into a MemberType pointer for a max_items=1 +// XML node. +func OneStrToMem(e string) *MemberType { + if e == "" { + return nil + } + + return &MemberType{[]Member{ + {Value: e}, + }} +} diff --git a/assets/util/pangoclient.go b/assets/util/pangoclient.go new file mode 100644 index 00000000..0f2a3338 --- /dev/null +++ b/assets/util/pangoclient.go @@ -0,0 +1,21 @@ +package util + +import ( + "context" + "net/http" + "net/url" + + "github.com/PaloAltoNetworks/pango/plugin" + "github.com/PaloAltoNetworks/pango/version" + "github.com/PaloAltoNetworks/pango/xmlapi" +) + +type PangoClient interface { + Versioning() version.Number + GetTarget() string + Plugins() []plugin.Info + MultiConfig(context.Context, *xmlapi.MultiConfig, bool, url.Values) ([]byte, *http.Response, *xmlapi.MultiConfigResponse, error) + Communicate(context.Context, PangoCommand, bool, any) ([]byte, *http.Response, error) + CommunicateFile(context.Context, string, string, string, url.Values, bool, any) ([]byte, *http.Response, error) + ReadFromConfig(context.Context, []string, bool, any) ([]byte, error) +} diff --git a/assets/util/pangocommand.go b/assets/util/pangocommand.go new file mode 100644 index 00000000..362c2f3b --- /dev/null +++ b/assets/util/pangocommand.go @@ -0,0 +1,9 @@ +package util + +import ( + "net/url" +) + +type PangoCommand interface { + AsUrlValues() (url.Values, error) +} diff --git a/assets/util/retriever.go b/assets/util/retriever.go new file mode 100644 index 00000000..4d4c1459 --- /dev/null +++ b/assets/util/retriever.go @@ -0,0 +1,5 @@ +package util + +// Retriever is a type that is intended to act as a stand-in for using +// either the Get or Show pango Client functions. +type Retriever func(interface{}, interface{}, interface{}) ([]byte, error) diff --git a/assets/util/util.go b/assets/util/util.go new file mode 100644 index 00000000..2a21a3e3 --- /dev/null +++ b/assets/util/util.go @@ -0,0 +1,282 @@ +// Package util contains various shared structs and functions used across +// the pango package. +package util + +import ( + "bytes" + "encoding/xml" + "fmt" + "regexp" + "strings" +) + +// VsysEntryType defines an entry config node with vsys entries underneath. +type VsysEntryType struct { + Entries []VsysEntry `xml:"entry"` +} + +// VsysEntry defines the "vsys" xpath node under a VsysEntryType config node. +type VsysEntry struct { + XMLName xml.Name `xml:"entry"` + Serial string `xml:"name,attr"` + Vsys *EntryType `xml:"vsys"` +} + +// VsysEntToMap normalizes a VsysEntryType pointer into a map. +func VsysEntToMap(ve *VsysEntryType) map[string][]string { + if ve == nil { + return nil + } + + ans := make(map[string][]string) + for i := range ve.Entries { + ans[ve.Entries[i].Serial] = EntToStr(ve.Entries[i].Vsys) + } + + return ans +} + +// MapToVsysEnt converts a map into a VsysEntryType pointer. +// +// This struct is used for "Target" information on Panorama when dealing with +// various policies. Maps are unordered, but FWICT Panorama doesn't seem to +// order anything anyways when doing things in the GUI, so hopefully this is +// ok...? +func MapToVsysEnt(e map[string][]string) *VsysEntryType { + if len(e) == 0 { + return nil + } + + i := 0 + ve := make([]VsysEntry, len(e)) + for key := range e { + ve[i].Serial = key + ve[i].Vsys = StrToEnt(e[key]) + i++ + } + + return &VsysEntryType{ve} +} + +// YesNo returns "yes" on true, "no" on false. +func YesNo(v bool) string { + if v { + return "yes" + } + return "no" +} + +// AsBool returns true on yes, else false. +func AsBool(val string) bool { + if val == "yes" { + return true + } + return false +} + +// AsXpath makes an xpath out of the given interface. +func AsXpath(i interface{}) string { + switch val := i.(type) { + case string: + return val + case []string: + return fmt.Sprintf("/%s", strings.Join(val, "/")) + default: + return "" + } +} + +// AsEntryXpath returns the given values as an entry xpath segment. +func AsEntryXpath(vals []string) string { + if len(vals) == 0 || (len(vals) == 1 && vals[0] == "") { + return "entry" + } + + var buf bytes.Buffer + + buf.WriteString("entry[") + for i := range vals { + if i != 0 { + buf.WriteString(" or ") + } + buf.WriteString("@name='") + buf.WriteString(vals[i]) + buf.WriteString("'") + } + buf.WriteString("]") + + return buf.String() +} + +// AsMemberXpath returns the given values as a member xpath segment. +func AsMemberXpath(vals []string) string { + var buf bytes.Buffer + + buf.WriteString("member[") + for i := range vals { + if i != 0 { + buf.WriteString(" or ") + } + buf.WriteString("text()='") + buf.WriteString(vals[i]) + buf.WriteString("'") + } + + buf.WriteString("]") + + return buf.String() +} + +// TemplateXpathPrefix returns the template xpath prefix of the given template name. +func TemplateXpathPrefix(tmpl, ts string) []string { + if tmpl != "" { + return []string{ + "config", + "devices", + AsEntryXpath([]string{"localhost.localdomain"}), + "template", + AsEntryXpath([]string{tmpl}), + } + } + + return []string{ + "config", + "devices", + AsEntryXpath([]string{"localhost.localdomain"}), + "template-stack", + AsEntryXpath([]string{ts}), + } +} + +// DeviceGroupXpathPrefix returns a device group xpath prefix. +// If the device group is empty, then the default is "shared". +func DeviceGroupXpathPrefix(dg string) []string { + if dg == "" || dg == "shared" { + return []string{"config", "shared"} + } + + return []string{ + "config", + "devices", + AsEntryXpath([]string{"localhost.localdomain"}), + "device-group", + AsEntryXpath([]string{dg}), + } +} + +// VsysXpathPrefix returns a vsys xpath prefix. +func VsysXpathPrefix(vsys string) []string { + if vsys == "" { + vsys = "vsys1" + } else if vsys == "shared" { + return []string{"config", "shared"} + } + + return []string{ + "config", + "devices", + AsEntryXpath([]string{"localhost.localdomain"}), + "vsys", + AsEntryXpath([]string{vsys}), + } +} + +// PanoramaXpathPrefix returns the panorama xpath prefix. +func PanoramaXpathPrefix() []string { + return []string{ + "config", + "panorama", + } +} + +// StripPanosPackaging removes the response / result and an optional third +// containing XML tag from the given byte slice. +func StripPanosPackaging(input []byte, tag string) []byte { + var index int + gt := []byte(">") + lt := []byte("<") + + // Remove response. + index = bytes.Index(input, gt) + ans := input[index+1:] + index = bytes.LastIndex(ans, lt) + ans = ans[:index] + + // Remove result. + index = bytes.Index(ans, gt) + ans = ans[index+1:] + index = bytes.LastIndex(ans, lt) + ans = ans[:index] + + ans = bytes.TrimSpace(ans) + + if tag != "" { + if bytes.HasPrefix(ans, []byte("<"+tag+" ")) || bytes.HasPrefix(ans, []byte("<"+tag+">")) { + index = bytes.Index(ans, gt) + ans = ans[index+1:] + if len(ans) > 0 { + index = bytes.LastIndex(ans, lt) + ans = ans[:index] + ans = bytes.TrimSpace(ans) + } + } + } + + return ans +} + +// CdataText is for getting CDATA contents of XML docs. +type CdataText struct { + Text string `xml:",cdata"` +} + +// RawXml is what allows the use of Edit commands on a XPATH without +// truncating any other child objects that may be attached to it. +type RawXml struct { + Text string `xml:",innerxml"` +} + +// CleanRawXml removes extra XML attributes from RawXml objects without +// requiring us to have to parse everything. +func CleanRawXml(v string) string { + re := regexp.MustCompile(` admin="\S+" dirtyId="\d+" time="\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}"`) + return re.ReplaceAllString(v, "") +} + +// ValidMovement returns if the movement constant is valid or not. +func ValidMovement(v int) bool { + switch v { + case MoveSkip, MoveBefore, MoveDirectlyBefore, MoveAfter, MoveDirectlyAfter, MoveTop, MoveBottom: + return true + } + + return false +} + +// RelativeMovement returns if the movement constant is a relative movement. +func RelativeMovement(v int) bool { + switch v { + case MoveBefore, MoveDirectlyBefore, MoveAfter, MoveDirectlyAfter: + return true + } + + return false +} + +// ValidateRulebase validates the device group and rulebase pairing for +// Panorama policies. +func ValidateRulebase(dg, base string) error { + switch base { + case "": + return fmt.Errorf("rulebase must be specified") + case Rulebase: + if dg != "shared" { + return fmt.Errorf("rulebase %q requires \"shared\" device group", base) + } + case PreRulebase, PostRulebase: + default: + return fmt.Errorf("unknown rulebase %q", base) + } + + return nil +} diff --git a/assets/util/util_test.go b/assets/util/util_test.go new file mode 100644 index 00000000..08126d0d --- /dev/null +++ b/assets/util/util_test.go @@ -0,0 +1,224 @@ +package util + +import ( + "bytes" + "fmt" + "testing" +) + +func TestMemToStrNil(t *testing.T) { + r := MemToStr(nil) + if r != nil { + t.Fail() + } +} + +func TestEntToStrNil(t *testing.T) { + r := EntToStr(nil) + if r != nil { + t.Fail() + } +} + +func TestStrToMem(t *testing.T) { + v := []string{"one", "two"} + r := StrToMem(v) + if r == nil { + t.Fail() + } else if len(v) != len(r.Members) { + t.Fail() + } else { + for i := range v { + if v[i] != r.Members[i].Value { + t.Fail() + break + } + } + } +} + +func TestStrToEnt(t *testing.T) { + v := []string{"one", "two"} + r := StrToEnt(v) + if r == nil { + t.Fail() + } else if len(v) != len(r.Entries) { + t.Fail() + } else { + for i := range v { + if v[i] != r.Entries[i].Value { + t.Fail() + break + } + } + } +} + +func BenchmarkStrToMem(b *testing.B) { + v := []string{"one", "two", "three", "four", "five"} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = StrToMem(v) + } +} + +func BenchmarkMemToStr(b *testing.B) { + m := &MemberType{[]Member{ + {Value: "one"}, + {Value: "two"}, + {Value: "three"}, + {Value: "four"}, + {Value: "five"}, + }} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = MemToStr(m) + } +} + +func BenchmarkStrToEnt(b *testing.B) { + v := []string{"one", "two", "three", "four", "five"} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = StrToEnt(v) + } +} + +func BenchmarkEntToStr(b *testing.B) { + v := &EntryType{[]Entry{ + {Value: "one"}, + {Value: "two"}, + {Value: "three"}, + {Value: "four"}, + {Value: "five"}, + }} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = EntToStr(v) + } +} + +func BenchmarkAsXpath(b *testing.B) { + p := []string{ + "config", + "devices", + AsEntryXpath([]string{"localhost.localdomain"}), + "vsys", + AsEntryXpath([]string{"vsys1"}), + "import", + "network", + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = AsXpath(p) + } +} + +func BenchmarkAsEntryXpathMultiple(b *testing.B) { + v := []string{"one", "two", "three"} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = AsEntryXpath(v) + } +} + +func TestAsXpath(t *testing.T) { + testCases := []struct { + i interface{} + r string + }{ + {"/one/two", "/one/two"}, + {[]string{"one", "two"}, "/one/two"}, + {42, ""}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%v to %s", tc.i, tc.r), func(t *testing.T) { + if AsXpath(tc.i) != tc.r { + t.Fail() + } + }) + } +} + +func TestAsEntryXpath(t *testing.T) { + testCases := []struct { + v []string + r string + }{ + {[]string{"one"}, "entry[@name='one']"}, + {[]string{"one", "two"}, "entry[@name='one' or @name='two']"}, + {nil, "entry"}, + } + + for _, tc := range testCases { + t.Run(tc.r, func(t *testing.T) { + if AsEntryXpath(tc.v) != tc.r { + t.Fail() + } + }) + } +} + +func TestAsMemberXpath(t *testing.T) { + testCases := []struct { + v []string + r string + }{ + {[]string{"one"}, "member[text()='one']"}, + {[]string{"one", "two"}, "member[text()='one' or text()='two']"}, + {nil, "member[]"}, + } + + for _, tc := range testCases { + t.Run(tc.r, func(t *testing.T) { + if AsMemberXpath(tc.v) != tc.r { + t.Fail() + } + }) + } +} + +func TestCleanRawXml(t *testing.T) { + v := `hi` + if CleanRawXml(v) != "hi" { + t.Fail() + } +} + +func TestStripPanosPackagingNoTag(t *testing.T) { + expected := "" + input := fmt.Sprintf("%s", expected) + + ans := StripPanosPackaging([]byte(input), "") + if !bytes.Equal([]byte(expected), ans) { + t.Errorf("Expected %q, got %q", expected, ans) + } +} + +func TestStripPanosPackagingWithTag(t *testing.T) { + expected := "" + input := fmt.Sprintf("%s", expected) + + ans := StripPanosPackaging([]byte(input), "outer") + if !bytes.Equal([]byte(expected), ans) { + t.Errorf("Expected %q, got %q", expected, ans) + } +} + +func TestStripPanosPackagingNoResult(t *testing.T) { + input := ` + +` + + ans := StripPanosPackaging([]byte(input), "interface") + if len(ans) != 0 { + t.Errorf("Expected empty string, got %q", ans) + } +} diff --git a/assets/util/xapiclient.go b/assets/util/xapiclient.go new file mode 100644 index 00000000..992d21b7 --- /dev/null +++ b/assets/util/xapiclient.go @@ -0,0 +1,52 @@ +package util + +import ( + "time" + + "github.com/PaloAltoNetworks/pango/plugin" + "github.com/PaloAltoNetworks/pango/version" +) + +// XapiClient is the interface that describes an pango.Client. +type XapiClient interface { + String() string + Versioning() version.Number + Plugins() []plugin.Info + + // Logging functions. + LogAction(string, ...interface{}) + LogQuery(string, ...interface{}) + LogOp(string, ...interface{}) + LogUid(string, ...interface{}) + LogLog(string, ...interface{}) + LogExport(string, ...interface{}) + LogImport(string, ...interface{}) + + // PAN-OS API calls. + Op(interface{}, string, interface{}, interface{}) ([]byte, error) + Show(interface{}, interface{}, interface{}) ([]byte, error) + Get(interface{}, interface{}, interface{}) ([]byte, error) + Delete(interface{}, interface{}, interface{}) ([]byte, error) + Set(interface{}, interface{}, interface{}, interface{}) ([]byte, error) + Edit(interface{}, interface{}, interface{}, interface{}) ([]byte, error) + Move(interface{}, string, string, interface{}, interface{}) ([]byte, error) + Log(string, string, string, string, int, int, interface{}, interface{}) ([]byte, error) + Export(string, time.Duration, interface{}, interface{}) (string, []byte, error) + Import(string, string, string, string, time.Duration, interface{}, interface{}) ([]byte, error) + Commit(interface{}, string, interface{}) (uint, []byte, error) + Uid(interface{}, string, interface{}, interface{}) ([]byte, error) + + // Vsys importables. + VsysImport(string, string, string, string, []string) error + VsysUnimport(string, string, string, []string) error + + // Extras. + EntryListUsing(Retriever, []string) ([]string, error) + MemberListUsing(Retriever, []string) ([]string, error) + RequestPasswordHash(string) (string, error) + WaitForJob(uint, time.Duration, interface{}, interface{}) error + WaitForLogs(uint, time.Duration, time.Duration, interface{}) ([]byte, error) + Clock() (time.Time, error) + PositionFirstEntity(int, string, string, []string, []string) error + ConfigTree() *XmlNode +} diff --git a/assets/util/xmlnode.go b/assets/util/xmlnode.go new file mode 100644 index 00000000..f6f3410d --- /dev/null +++ b/assets/util/xmlnode.go @@ -0,0 +1,54 @@ +package util + +import ( + "encoding/xml" + "strings" +) + +const ( + entryPrefix = "entry[@name='" + entrySuffix = "']" +) + +// XmlNode is a generic XML node. +type XmlNode struct { + XMLName xml.Name + Attributes []xml.Attr `xml:",any,attr"` + Text []byte `xml:",innerxml"` + Nodes []XmlNode `xml:",any"` +} + +// FindXmlNodeInTree finds a given path in the specified XmlNode tree. +func FindXmlNodeInTree(path []string, elm *XmlNode) *XmlNode { + if len(path) == 0 { + return elm + } + + if elm == nil { + return elm + } + + tag := path[0] + path = path[1:] + var name string + if strings.HasPrefix(tag, entryPrefix) { + name = strings.TrimSuffix(strings.TrimPrefix(tag, entryPrefix), entrySuffix) + tag = "entry" + } + + for _, x := range elm.Nodes { + if x.XMLName.Local == tag { + if name == "" { + return FindXmlNodeInTree(path, &x) + } else { + for _, atr := range x.Attributes { + if atr.Name.Local == "name" && atr.Value == name { + return FindXmlNodeInTree(path, &x) + } + } + } + } + } + + return nil +} diff --git a/assets/version/version.go b/assets/version/version.go new file mode 100644 index 00000000..b982d9d1 --- /dev/null +++ b/assets/version/version.go @@ -0,0 +1,72 @@ +// Package version contains a version number struct that pango uses to make +// decisions on the specific structs to use when sending XML to the PANOS +// device. +package version + +import ( + "fmt" + "strconv" + "strings" +) + +// Number is the version number struct. +type Number struct { + Major, Minor, Patch int + Suffix string +} + +// Gte tests if this version number is greater than or equal to the argument. +func (v Number) Gte(o Number) 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 Number) String() string { + if v.Suffix == "" { + 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.Suffix) + } +} + +// New returns a version number from the given string. +func New(version string) (Number, error) { + parts := strings.Split(version, ".")[:3] + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return Number{}, fmt.Errorf("Major %s is not a number: %s", parts[0], err) + } + + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return Number{}, fmt.Errorf("Minor %s is not a number: %s", parts[0], err) + } + + var patch_str string + var suffix string + patch_parts := strings.Split(parts[2], "-") + if len(patch_parts) == 1 { + patch_str = parts[2] + suffix = "" + } else if len(patch_parts) == 2 { + patch_str = patch_parts[0] + suffix = patch_parts[1] + } else { + return Number{}, fmt.Errorf("Patch %s is not formatted as expected", parts[2]) + } + patch, err := strconv.Atoi(patch_str) + if err != nil { + return Number{}, fmt.Errorf("Patch %s is not a number: %s", patch_str, err) + } + + return Number{major, minor, patch, suffix}, nil +} diff --git a/assets/version/version_test.go b/assets/version/version_test.go new file mode 100644 index 00000000..547d5aa3 --- /dev/null +++ b/assets/version/version_test.go @@ -0,0 +1,124 @@ +package version + +import ( + "fmt" + "testing" +) + +func TestNew(t *testing.T) { + testCases := []struct { + s string + r bool + a, b, c int + d string + }{ + {"1.2.3", false, 1, 2, 3, ""}, + {"1.2.3-h4", false, 1, 2, 3, "h4"}, + {"12.34.56-78h", false, 12, 34, 56, "78h"}, + {"a.2.3", true, 0, 0, 0, ""}, + {"1.b.3", true, 0, 0, 0, ""}, + {"1.2.c", true, 0, 0, 0, ""}, + {"1.2.3h4", true, 0, 0, 0, ""}, + {"9.0.3.xfr", false, 9, 0, 3, ""}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s should error %t", tc.s, tc.r), func(t *testing.T) { + v, err := New(tc.s) + if (err != nil) != tc.r || v.Major != tc.a || v.Minor != tc.b || v.Patch != tc.c || v.Suffix != tc.d { + t.Fail() + } + }) + } +} + +func TestStringer(t *testing.T) { + testCases := []struct { + a, b, c int + d, want string + }{ + {1, 2, 3, "", "1.2.3"}, + {1, 2, 3, "h4", "1.2.3-h4"}, + {12, 34, 56, "h78", "12.34.56-h78"}, + } + + for _, tc := range testCases { + t.Run(tc.want, func(t *testing.T) { + v := Number{tc.a, tc.b, tc.c, tc.d} + if v.String() != tc.want { + t.Fail() + } + }) + } +} + +func TestGte(t *testing.T) { + testCases := []struct { + a, b, c int + r bool + }{ + {1, 1, 1, true}, + {1, 1, 2, true}, + {1, 1, 3, true}, + {1, 2, 1, true}, + {1, 2, 2, true}, + {1, 2, 3, true}, + {1, 3, 1, true}, + {1, 3, 2, true}, + {1, 3, 3, true}, + {2, 1, 1, true}, + {2, 1, 2, true}, + {2, 1, 3, true}, + {2, 2, 1, true}, + {2, 2, 2, true}, + {2, 2, 3, false}, + {2, 3, 1, false}, + {2, 3, 2, false}, + {2, 3, 3, false}, + {3, 1, 1, false}, + {3, 1, 2, false}, + {3, 1, 3, false}, + {3, 2, 1, false}, + {3, 2, 2, false}, + {3, 2, 3, false}, + {3, 3, 1, false}, + {3, 3, 2, false}, + {3, 3, 3, false}, + } + v1 := Number{2, 2, 2, ""} + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s >= %d.%d.%d == %t", v1, tc.a, tc.b, tc.c, tc.r), func(t *testing.T) { + r := v1.Gte(Number{tc.a, tc.b, tc.c, ""}) + if r != tc.r { + t.Fail() + } + }) + } +} + +func BenchmarkGteMajor(b *testing.B) { + v1 := Number{5, 5, 5, ""} + v2 := Number{6, 5, 5, ""} + + for i := 0; i < b.N; i++ { + _ = v1.Gte(v2) + } +} + +func BenchmarkGtePatch(b *testing.B) { + v1 := Number{5, 5, 5, ""} + v2 := Number{5, 5, 6, ""} + + for i := 0; i < b.N; i++ { + _ = v1.Gte(v2) + } +} + +func BenchmarkNew(b *testing.B) { + s := "7.1.12-h4" + + for i := 0; i < b.N; i++ { + _, _ = New(s) + } +} From 0824d64a814bdc964aa4f6d733c97cc738f98dd6 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 13:36:06 +0100 Subject: [PATCH 20/32] add function to copy static assets --- cmd/mktp/config.yaml | 12 ++++++ pkg/generate/asset.go | 94 +++++++++++++++++++++++++++++++++++++++++++ pkg/mktp/cmd.go | 5 +++ 3 files changed, 111 insertions(+) create mode 100644 pkg/generate/asset.go diff --git a/cmd/mktp/config.yaml b/cmd/mktp/config.yaml index b2b20da3..95ff5e40 100644 --- a/cmd/mktp/config.yaml +++ b/cmd/mktp/config.yaml @@ -8,3 +8,15 @@ assets: go_sdk: true terraform_provider: false destination: "util" + errors_package: + source: "assets/errors" + target: + go_sdk: true + terraform_provider: false + destination: "errors" + version_package: + source: "assets/version" + target: + go_sdk: true + terraform_provider: false + destination: "version" diff --git a/pkg/generate/asset.go b/pkg/generate/asset.go new file mode 100644 index 00000000..331a0112 --- /dev/null +++ b/pkg/generate/asset.go @@ -0,0 +1,94 @@ +package generate + +import ( + "bytes" + "fmt" + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "io" + "io/fs" + "os" + "path/filepath" +) + +func CopyAssets(config *properties.Config) error { + for _, asset := range config.Assets { + files, err := listAssets(asset) + if err != nil { + return err + } + + if asset.Target.GoSdk { + if err = copyAsset(config.Output.GoSdk, asset, files); err != nil { + return err + } + } + if asset.Target.TerraformProvider { + if err = copyAsset(config.Output.TerraformProvider, asset, files); err != nil { + return err + } + } + } + + return nil +} + +func listAssets(asset *properties.Asset) ([]string, error) { + var files []string + + // Walk through directory and get list of all files + err := filepath.WalkDir(asset.Source, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + if !entry.IsDir() { + files = append(files, path) + } + return nil + }) + if err != nil { + return nil, err + } + + return files, nil +} + +func copyAsset(target string, asset *properties.Asset, files []string) error { + // Prepare destination path + destinationDir := target + "/" + asset.Destination + + // Create the destination directory if it doesn't exist + if err := os.MkdirAll(destinationDir, os.ModePerm); err != nil { + return err + } + + for _, sourceFilePath := range files { + // Prepare destination path + destinationFilePath := filepath.Join(destinationDir, filepath.Base(sourceFilePath)) + fmt.Printf("Copy file from %s to %s\n", sourceFilePath, destinationFilePath) + + // Read the contents of the source file + data, err := os.ReadFile(sourceFilePath) + if err != nil { + return err + } + + // Create the destination file + destinationFile, err := os.Create(destinationFilePath) + if err != nil { + return err + } + defer func(destinationFile *os.File) { + err := destinationFile.Close() + if err != nil { + + } + }(destinationFile) + + // Write the contents into the destination file + _, err = io.Copy(destinationFile, bytes.NewReader(data)) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/mktp/cmd.go b/pkg/mktp/cmd.go index c87012bc..66d3960e 100644 --- a/pkg/mktp/cmd.go +++ b/pkg/mktp/cmd.go @@ -109,6 +109,11 @@ func (c *Cmd) Execute() error { // Output as Terraform code. } + // Copy assets (static files) + if err = generate.CopyAssets(config); err != nil { + return fmt.Errorf("error copying assets %s", err) + } + // Finalize pango code: // * make fmt From 54f99e30ff3ae5ffffdf7e2fb89e9e05c199bab7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 6 Mar 2024 15:04:28 +0100 Subject: [PATCH 21/32] change go sdk path for service and address group --- pkg/properties/normalized.go | 11 ----------- specs/objects/address-group.yaml | 3 ++- specs/objects/service-group.yaml | 3 ++- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 936fceeb..c2861620 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -197,14 +197,3 @@ func (spec *Normalization) Validate() []error { return checks } - -//func (spec *Normalization) GetCamelCaseLocations() map[string]*Location { -// locations := map[string]*Location{} -// -// for key, location := range spec.Locations { -// camelCaseKey := naming.CamelCase("", key, "", true) -// locations[camelCaseKey] = location -// } -// -// return locations -//} diff --git a/specs/objects/address-group.yaml b/specs/objects/address-group.yaml index 830eb3ca..90005d15 100644 --- a/specs/objects/address-group.yaml +++ b/specs/objects/address-group.yaml @@ -2,7 +2,8 @@ name: 'Address group' terraform_provider_suffix: 'address_group' go_sdk_path: - 'objects' - - 'address_group' + - 'address' + - 'group' xpath_suffix: - 'address-group' locations: diff --git a/specs/objects/service-group.yaml b/specs/objects/service-group.yaml index 5fc79909..adbadf0b 100644 --- a/specs/objects/service-group.yaml +++ b/specs/objects/service-group.yaml @@ -2,7 +2,8 @@ name: 'Service group' terraform_provider_suffix: 'service_group' go_sdk_path: - 'objects' - - 'service_group' + - 'service' + - 'group' xpath_suffix: - 'service-group' locations: From f34380e3c7f320a18262fb362604a71615547a7b Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 09:40:37 +0100 Subject: [PATCH 22/32] Add generic.Xml --- assets/generic/xml.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 assets/generic/xml.go diff --git a/assets/generic/xml.go b/assets/generic/xml.go new file mode 100644 index 00000000..f0d32f98 --- /dev/null +++ b/assets/generic/xml.go @@ -0,0 +1,39 @@ +package generic + +import ( + "encoding/xml" + "strings" +) + +// Xml is a generic catch-all for parsing XML returned from PAN-OS. +type Xml struct { + XMLName xml.Name + Name *string `xml:"name,attr,omitempty"` + Uuid *string `xml:"uuid,attr,omitempty"` + Text []byte `xml:",chardata"` + Nodes []Xml `xml:",any"` + + // TrimmedText contains the trimmed value of Text. Note that since this could + // very well be trimming legitimate spacing that the text field would otherwise + // contain, refering to this field for anything other than debugging purposes is + // probably not a good idea. + TrimmedText *string `xml:"-"` +} + +func (e *Xml) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type local Xml + var ans local + if err := d.DecodeElement(&ans, &start); err != nil { + return err + } + + if len(ans.Text) != 0 { + v := strings.TrimSpace(string(ans.Text)) + if v != "" { + ans.TrimmedText = &v + } + } + + *e = Xml(ans) + return nil +} From 73a3d435af1989dd9a3c3a3f8c9bf0d0ba656b65 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 12:46:10 +0100 Subject: [PATCH 23/32] generate part of entry.go - struct Entry --- pkg/generate/generator.go | 10 +++++--- pkg/properties/normalized.go | 23 +++++++++++++++++++ pkg/properties/normalized_test.go | 14 ++++++++---- pkg/translate/structs.go | 19 ++++++++++++++++ pkg/translate/structs_test.go | 28 +++++++++++++++++++++++ templates/sdk/entry.tmpl | 38 ++++++++++++++++++++++++++++++- 6 files changed, 124 insertions(+), 8 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 9d923931..58aac891 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -75,12 +75,16 @@ func (c *Creator) generateOutputFileFromTemplate(tmpl *template.Template, output func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := fmt.Sprintf("%s/%s", c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, - "locationType": translate.LocationType, - "omitEmpty": translate.OmitEmpty, + "packageName": translate.PackageName, + "locationType": translate.LocationType, + "specParamType": translate.SpecParamType, + "omitEmpty": translate.OmitEmpty, "contains": func(full, part string) bool { return strings.Contains(full, part) }, + "subtract": func(a, b int) int { + return a - b + }, "asEntryXpath": translate.AsEntryXpath, } tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index c2861620..65a52602 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -74,6 +74,7 @@ type Spec struct { type SpecParam struct { Description string `json:"description" yaml:"description"` Type string `json:"type" yaml:"type"` + Required bool `json:"required" yaml:"required"` Length *SpecParamLength `json:"length" yaml:"length,omitempty"` Count *SpecParamCount `json:"count" yaml:"count,omitempty"` Items *SpecParamItems `json:"items" yaml:"items,omitempty"` @@ -142,6 +143,8 @@ func ParseSpec(input []byte) (*Normalization, error) { err = spec.AddNameVariants() + err = spec.AddDefaultTypesForParams() + return &spec, err } @@ -163,6 +166,26 @@ func (spec *Normalization) AddNameVariants() error { return nil } +func (spec *Normalization) AddDefaultTypesForParams() error { + if spec.Spec != nil { + if spec.Spec.Params != nil { + for _, param := range spec.Spec.Params { + if param.Type == "" { + param.Type = "string" + } + } + } + if spec.Spec.OneOf != nil { + for _, param := range spec.Spec.OneOf { + if param.Type == "" { + param.Type = "string" + } + } + } + } + return nil +} + func (spec *Normalization) Sanity() error { if spec.Name == "" { return errors.New("name is required") diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 7d3a8b72..2de596dd 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -255,6 +255,7 @@ spec: description: description: The description. type: string + required: false length: min: 0 max: 1023 @@ -265,6 +266,7 @@ spec: tags: description: The administrative tags. type: list + required: false count: min: null max: 64 @@ -281,7 +283,8 @@ spec: one_of: fqdn: description: The FQDN value. - type: "" + type: string + required: false length: min: 1 max: 255 @@ -292,21 +295,24 @@ spec: spec: null ip_netmask: description: The IP netmask value. - type: "" + type: string + required: false profiles: - xpath: - ip-netmask spec: null ip_range: description: The IP range value. - type: "" + type: string + required: false profiles: - xpath: - ip-range spec: null ip_wildcard: description: The IP wildcard value. - type: "" + type: string + required: false profiles: - xpath: - ip-wildcard diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 33e21fb4..67c808ba 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -16,6 +16,25 @@ func LocationType(location *properties.Location, pointer bool) string { } } +func SpecParamType(param *properties.SpecParam) string { + prefix := "" + if !param.Required { + prefix = "*" + } + if param.Type == "list" { + prefix = "[]" + } + + calculatedType := "" + if param.Type == "list" && param.Items != nil { + calculatedType = param.Items.Type + } else { + calculatedType = param.Type + } + + return prefix + calculatedType +} + func OmitEmpty(location *properties.Location) string { if location.Vars != nil { return ",omitempty" diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 6bd61b31..13e140d9 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -67,6 +67,34 @@ func TestLocationType(t *testing.T) { assert.Contains(t, locationTypes, "bool") } +func TestSpecParamType(t *testing.T) { + // given + paramTypeRequiredString := properties.SpecParam{ + Type: "string", + Required: true, + } + itemsForParam := properties.SpecParamItems{ + Type: "string", + } + paramTypeListString := properties.SpecParam{ + Type: "list", + Items: &itemsForParam, + } + paramTypeOptionalString := properties.SpecParam{ + Type: "string", + } + + // when + calculatedTypeRequiredString := SpecParamType(¶mTypeRequiredString) + calculatedTypeListString := SpecParamType(¶mTypeListString) + calculatedTypeOptionalString := SpecParamType(¶mTypeOptionalString) + + // then + assert.Equal(t, "string", calculatedTypeRequiredString) + assert.Equal(t, "[]string", calculatedTypeListString) + assert.Equal(t, "*string", calculatedTypeOptionalString) +} + func TestOmitEmpty(t *testing.T) { // given yamlParsedData, _ := properties.ParseSpec([]byte(sampleSpec)) diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 3b3d1518..171b29c6 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -1 +1,37 @@ -package {{packageName .GoSdkPath}} \ No newline at end of file +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 { + {{- if .Entry}} + Name string + {{- end}} + {{- range $key, $param := .Spec.Params}} + {{$key}} {{specParamType $param}} + {{- end}} + {{- range $key, $param := .Spec.OneOf}} + {{$key}} {{specParamType $param}} + {{- end}} + + Misc map[string][]generic.Xml +} From a3b31d9b57f7677a03b0d0e140c802b245003112 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 14:48:24 +0100 Subject: [PATCH 24/32] add tests for names --- pkg/naming/names_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pkg/naming/names_test.go diff --git a/pkg/naming/names_test.go b/pkg/naming/names_test.go new file mode 100644 index 00000000..e4537915 --- /dev/null +++ b/pkg/naming/names_test.go @@ -0,0 +1,16 @@ +package naming + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCamelCase(t *testing.T) { + assert.Equal(t, "CamelCase", CamelCase("", "camel_case", "", true)) + assert.Equal(t, "camelCase", CamelCase("", "camel_case", "", false)) +} + +func TestAlphaNumeric(t *testing.T) { + assert.Equal(t, "alphanumeric", AlphaNumeric("alpha_numeric")) + assert.Equal(t, "AlphaNumeric", AlphaNumeric("Alpha_Numeric")) +} From 5b07e8bee804980d7829b29fd8eeef8daebd2372 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 14:49:50 +0100 Subject: [PATCH 25/32] add generic package --- assets/generic/xml.go | 15 ++--- assets/generic/xml_test.go | 117 +++++++++++++++++++++++++++++++++++++ cmd/mktp/config.yaml | 6 ++ 3 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 assets/generic/xml_test.go diff --git a/assets/generic/xml.go b/assets/generic/xml.go index f0d32f98..cae21261 100644 --- a/assets/generic/xml.go +++ b/assets/generic/xml.go @@ -7,11 +7,12 @@ import ( // Xml is a generic catch-all for parsing XML returned from PAN-OS. type Xml struct { - XMLName xml.Name - Name *string `xml:"name,attr,omitempty"` - Uuid *string `xml:"uuid,attr,omitempty"` - Text []byte `xml:",chardata"` - Nodes []Xml `xml:",any"` + XMLName xml.Name + Name *string `xml:"name,attr,omitempty"` + Uuid *string `xml:"uuid,attr,omitempty"` + DetailedVersion *string `xml:"detail-version,attr,omitempty"` + Text []byte `xml:",chardata"` + Nodes []Xml `xml:",any"` // TrimmedText contains the trimmed value of Text. Note that since this could // very well be trimming legitimate spacing that the text field would otherwise @@ -20,7 +21,7 @@ type Xml struct { TrimmedText *string `xml:"-"` } -func (e *Xml) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { +func (x *Xml) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { type local Xml var ans local if err := d.DecodeElement(&ans, &start); err != nil { @@ -34,6 +35,6 @@ func (e *Xml) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { } } - *e = Xml(ans) + *x = Xml(ans) return nil } diff --git a/assets/generic/xml_test.go b/assets/generic/xml_test.go new file mode 100644 index 00000000..b01f5eef --- /dev/null +++ b/assets/generic/xml_test.go @@ -0,0 +1,117 @@ +package generic + +import ( + "encoding/xml" + "strings" + "testing" +) + +var ( + TestXml = []byte(` + + some text + + + + + +`) +) + +func TestUnmarshalDoesNotReturnError(t *testing.T) { + var x Xml + if err := xml.Unmarshal(TestXml, &x); err != nil { + t.Fatalf("Error in unmarshal: %s", err) + } +} + +func TestUnmarshalSavesName(t *testing.T) { + var x Xml + if err := xml.Unmarshal(TestXml, &x); err != nil { + t.Fatalf("Error in unmarshal: %s", err) + } + if len(x.Nodes) == 0 { + t.Fatalf("no nodes present") + } + for _, node := range x.Nodes { + if node.XMLName.Local == "person" { + if node.Name == nil { + t.Fatalf("config > person.name is nil") + } + if *node.Name != "jane" { + t.Fatalf("config > person.name = %q", *node.Name) + } + return + } + } + + t.Fatalf("could not find config > person") +} + +func TestUnmarshalSavesUuid(t *testing.T) { + var x Xml + if err := xml.Unmarshal(TestXml, &x); err != nil { + t.Fatalf("Error in unmarshal: %s", err) + } + if len(x.Nodes) == 0 { + t.Fatalf("no nodes present") + } + for _, node := range x.Nodes { + if node.XMLName.Local == "unique" { + if node.Uuid == nil { + t.Fatalf("config > unique.uuid is nil") + } + if *node.Uuid != "1234-56-789" { + t.Fatalf("config > unique.uuid = %q", *node.Uuid) + } + return + } + } + + t.Fatalf("could not find config > unique") +} + +func TestUnmarshalSavesText(t *testing.T) { + var x Xml + if err := xml.Unmarshal(TestXml, &x); err != nil { + t.Fatalf("Error in unmarshal: %s", err) + } + if len(x.Nodes) == 0 { + t.Fatalf("no nodes present") + } + for _, node := range x.Nodes { + if node.XMLName.Local == "normal" { + if len(node.Text) == 0 { + t.Fatalf("config > normal.text is empty") + } + if string(node.Text) != " some text " { + t.Fatalf("config > normal.text = %q", *node.Uuid) + } + return + } + } + + t.Fatalf("could not find config > normal") +} + +func TestMarshalDoesNotContainThings(t *testing.T) { + var x Xml + if err := xml.Unmarshal(TestXml, &x); err != nil { + t.Fatalf("Error in unmarshal: %s", err) + } + + b, err := xml.Marshal(x) + if err != nil { + t.Fatalf("Error in marshal: %s", err) + } + + ans := string(b) + + missing := []string{"10.2.0", "foobar", "Node"} + + for _, chk := range missing { + if strings.Contains(ans, chk) { + t.Fatalf("marshalled bytes includes %q: %s", chk, ans) + } + } +} diff --git a/cmd/mktp/config.yaml b/cmd/mktp/config.yaml index 95ff5e40..2c994572 100644 --- a/cmd/mktp/config.yaml +++ b/cmd/mktp/config.yaml @@ -2,6 +2,12 @@ output: go_sdk: "../generated/pango" terraform_provider: "../generated/terraform-provider-panos" assets: + generic_package: + source: "assets/generic" + target: + go_sdk: true + terraform_provider: false + destination: "generic" util_package: source: "assets/util" target: From 2a8705c9f7f3fdbae3f19e18e6cef731a9be427b Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 16:11:52 +0100 Subject: [PATCH 26/32] add name variants for params --- pkg/properties/normalized.go | 43 ++++++++++++++++++++++++++++--- pkg/properties/normalized_test.go | 18 +++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 65a52602..23290be1 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -72,6 +72,7 @@ type Spec struct { } type SpecParam struct { + Name *NameVariant Description string `json:"description" yaml:"description"` Type string `json:"type" yaml:"type"` Required bool `json:"required" yaml:"required"` @@ -141,14 +142,14 @@ func ParseSpec(input []byte) (*Normalization, error) { err := content.Unmarshal(input, &spec) - err = spec.AddNameVariants() - + err = spec.AddNameVariantsForLocation() + err = spec.AddNameVariantsForParams() err = spec.AddDefaultTypesForParams() return &spec, err } -func (spec *Normalization) AddNameVariants() error { +func (spec *Normalization) AddNameVariantsForLocation() error { for key, location := range spec.Locations { location.Name = &NameVariant{ Underscore: key, @@ -166,6 +167,42 @@ func (spec *Normalization) AddNameVariants() error { return nil } +func AddNameVariantsForParams(name string, param *SpecParam) error { + param.Name = &NameVariant{ + Underscore: name, + CamelCase: naming.CamelCase("", name, "", true), + } + if param.Spec != nil { + for key, childParam := range param.Spec.Params { + if err := AddNameVariantsForParams(key, childParam); err != nil { + return err + } + } + for key, childParam := range param.Spec.OneOf { + if err := AddNameVariantsForParams(key, childParam); err != nil { + return err + } + } + } + return nil +} + +func (spec *Normalization) AddNameVariantsForParams() error { + if spec.Spec != nil { + for key, param := range spec.Spec.Params { + if err := AddNameVariantsForParams(key, param); err != nil { + return err + } + } + for key, param := range spec.Spec.OneOf { + if err := AddNameVariantsForParams(key, param); err != nil { + return err + } + } + } + return nil +} + func (spec *Normalization) AddDefaultTypesForParams() error { if spec.Spec != nil { if spec.Spec.Params != nil { diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 2de596dd..37401379 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -253,6 +253,9 @@ version: 10.1.0 spec: params: description: + name: + underscore: description + camelcase: Description description: The description. type: string required: false @@ -264,6 +267,9 @@ spec: - description spec: null tags: + name: + underscore: tags + camelcase: Tags description: The administrative tags. type: list required: false @@ -282,6 +288,9 @@ spec: spec: null one_of: fqdn: + name: + underscore: fqdn + camelcase: Fqdn description: The FQDN value. type: string required: false @@ -294,6 +303,9 @@ spec: - fqdn spec: null ip_netmask: + name: + underscore: ip_netmask + camelcase: IpNetmask description: The IP netmask value. type: string required: false @@ -302,6 +314,9 @@ spec: - ip-netmask spec: null ip_range: + name: + underscore: ip_range + camelcase: IpRange description: The IP range value. type: string required: false @@ -310,6 +325,9 @@ spec: - ip-range spec: null ip_wildcard: + name: + underscore: ip_wildcard + camelcase: IpWildcard description: The IP wildcard value. type: string required: false From 7b75458080129300bdcb2680854eeb1f67a80d5b Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 7 Mar 2024 16:12:03 +0100 Subject: [PATCH 27/32] start work on entry.go template --- templates/sdk/entry.tmpl | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 171b29c6..281aa840 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -26,12 +26,44 @@ type Entry struct { {{- if .Entry}} Name string {{- end}} - {{- range $key, $param := .Spec.Params}} - {{$key}} {{specParamType $param}} + {{- range $_, $param := .Spec.Params}} + {{$param.Name.CamelCase}} {{specParamType $param}} {{- end}} - {{- range $key, $param := .Spec.OneOf}} - {{$key}} {{specParamType $param}} + {{- range $_, $param := .Spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType $param}} {{- end}} Misc map[string][]generic.Xml } + +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]...) + } +} + +func (e *Entry) Field(v string) (any, error) { + {{- if .Entry}} + if v == "name" || v == "Name" { + return e.Name, nil + } + {{- end}} + + {{- range $_, $param := .Spec.Params}} + if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { + return e.{{$param.Name.CamelCase}}, nil + } + {{- end}} + {{- range $_, $param := .Spec.OneOf}} + if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { + return e.{{$param.Name.CamelCase}}, nil + } + {{- end}} + + return nil, fmt.Errorf("unknown field") +} \ No newline at end of file From 96e300a15e6af5b0fb29daf4776fc37c26af70e7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 8 Mar 2024 09:02:48 +0100 Subject: [PATCH 28/32] add error handling for func AsEntryXpath --- pkg/translate/funcs.go | 8 ++++++-- pkg/translate/funcs_test.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 10b04a4a..ec03252c 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -1,12 +1,16 @@ package translate import ( + "errors" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "strings" ) -func AsEntryXpath(location, xpath string) string { +func AsEntryXpath(location, xpath string) (string, error) { + if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { + return "", errors.New("$ followed by } should exists in xpath'") + } xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) xpath = naming.CamelCase("", xpath, "", true) - return "util.AsEntryXpath([]string{o." + location + "." + xpath + "})," + return "util.AsEntryXpath([]string{o." + location + "." + xpath + "}),", nil } diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index a47ede8a..fdb891ee 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -9,7 +9,7 @@ func TestAsEntryXpath(t *testing.T) { // given // when - asEntryXpath := AsEntryXpath("DeviceGroup", "{{ Entry $panorama_device }}") + asEntryXpath, _ := AsEntryXpath("DeviceGroup", "{{ Entry $panorama_device }}") // then assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) From 82b673b2b7f71ee438621db5e899273f49920497 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 8 Mar 2024 09:14:27 +0100 Subject: [PATCH 29/32] rename assets.go --- pkg/generate/{asset.go => assets.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/generate/{asset.go => assets.go} (100%) diff --git a/pkg/generate/asset.go b/pkg/generate/assets.go similarity index 100% rename from pkg/generate/asset.go rename to pkg/generate/assets.go From d67d35c4cfa6bd772e5dc51ee0fade625edc9195 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 8 Mar 2024 09:22:30 +0100 Subject: [PATCH 30/32] ignore error while closing file in defer --- pkg/generate/assets.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/generate/assets.go b/pkg/generate/assets.go index 331a0112..2b0c9904 100644 --- a/pkg/generate/assets.go +++ b/pkg/generate/assets.go @@ -78,10 +78,7 @@ func copyAsset(target string, asset *properties.Asset, files []string) error { return err } defer func(destinationFile *os.File) { - err := destinationFile.Close() - if err != nil { - - } + _ = destinationFile.Close() }(destinationFile) // Write the contents into the destination file From f1f1e8b72d4548ac97abcdde974128917bec3dd9 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 8 Mar 2024 09:23:25 +0100 Subject: [PATCH 31/32] remove files copied from pango --- assets/errors/panos.go | 140 ---------------- assets/errors/panos_test.go | 73 --------- assets/util/actioner.go | 5 - assets/util/bulk_element.go | 19 --- assets/util/comparison.go | 76 --------- assets/util/const.go | 39 ----- assets/util/copy.go | 31 ---- assets/util/elementer.go | 6 - assets/util/entry.go | 67 -------- assets/util/hitcount.go | 67 -------- assets/util/jobs.go | 71 --------- assets/util/license.go | 17 -- assets/util/lock.go | 15 -- assets/util/member.go | 68 -------- assets/util/pangoclient.go | 21 --- assets/util/pangocommand.go | 9 -- assets/util/retriever.go | 5 - assets/util/util.go | 282 --------------------------------- assets/util/util_test.go | 224 -------------------------- assets/util/xapiclient.go | 52 ------ assets/util/xmlnode.go | 54 ------- assets/version/version.go | 72 --------- assets/version/version_test.go | 124 --------------- cmd/mktp/config.yaml | 36 ++--- 24 files changed, 18 insertions(+), 1555 deletions(-) delete mode 100644 assets/errors/panos.go delete mode 100644 assets/errors/panos_test.go delete mode 100644 assets/util/actioner.go delete mode 100644 assets/util/bulk_element.go delete mode 100644 assets/util/comparison.go delete mode 100644 assets/util/const.go delete mode 100644 assets/util/copy.go delete mode 100644 assets/util/elementer.go delete mode 100644 assets/util/entry.go delete mode 100644 assets/util/hitcount.go delete mode 100644 assets/util/jobs.go delete mode 100644 assets/util/license.go delete mode 100644 assets/util/lock.go delete mode 100644 assets/util/member.go delete mode 100644 assets/util/pangoclient.go delete mode 100644 assets/util/pangocommand.go delete mode 100644 assets/util/retriever.go delete mode 100644 assets/util/util.go delete mode 100644 assets/util/util_test.go delete mode 100644 assets/util/xapiclient.go delete mode 100644 assets/util/xmlnode.go delete mode 100644 assets/version/version.go delete mode 100644 assets/version/version_test.go diff --git a/assets/errors/panos.go b/assets/errors/panos.go deleted file mode 100644 index b56e8c49..00000000 --- a/assets/errors/panos.go +++ /dev/null @@ -1,140 +0,0 @@ -package errors - -import ( - "encoding/xml" - stderr "errors" - "fmt" - "strings" - - "github.com/PaloAltoNetworks/pango/util" -) - -var InvalidFilterError = stderr.New("filter is improperly formatted") -var NameNotSpecifiedError = stderr.New("name is not specified") -var NoLocationSpecifiedError = stderr.New("no location specified") -var UnrecognizedOperatorError = stderr.New("unsupported filter operator") -var UnsupportedFilterTypeError = stderr.New("unsupported type for filtering") - -// Panos is an error returned from PAN-OS. -// -// The error contains both the error message and the code returned from PAN-OS. -type Panos struct { - Msg string - Code int -} - -// Error returns the error message. -func (e Panos) Error() string { - return e.Msg -} - -// ObjectNotFound returns true if this is an object not found error. -func (e Panos) ObjectNotFound() bool { - return e.Code == 7 -} - -// ObjectNotFound returns an object not found error. -func ObjectNotFound() Panos { - return Panos{ - Msg: "Object not found", - Code: 7, - } -} - -// Parse attempts to parse an error from the given XML response. -func Parse(body []byte) error { - var e errorCheck - - _ = xml.Unmarshal(body, &e) - if e.Failed() { - return Panos{ - Msg: e.Message(), - Code: e.Code, - } - } - - return nil -} - -type errorCheck struct { - XMLName xml.Name `xml:"response"` - Status string `xml:"status,attr"` - Code int `xml:"code,attr"` - Msg *errorCheckMsg `xml:"msg"` - ResultMsg *string `xml:"result>msg"` -} - -type errorCheckMsg struct { - Line []util.CdataText `xml:"line"` - Message string `xml:",chardata"` -} - -func (e *errorCheck) Failed() bool { - if e.Status == "failed" || e.Status == "error" { - return true - } else if e.Code == 0 || e.Code == 19 || e.Code == 20 { - return false - } - - return true -} - -func (e *errorCheck) Message() string { - if e.Msg != nil { - if len(e.Msg.Line) > 0 { - var b strings.Builder - for i := range e.Msg.Line { - if i != 0 { - b.WriteString(" | ") - } - b.WriteString(strings.TrimSpace(e.Msg.Line[i].Text)) - } - return b.String() - } - - if e.Msg.Message != "" { - return e.Msg.Message - } - } - - if e.ResultMsg != nil { - return *e.ResultMsg - } - - return e.CodeError() -} - -func (e *errorCheck) CodeError() string { - switch e.Code { - case 1: - return "Unknown command" - case 2, 3, 4, 5, 11: - return fmt.Sprintf("Internal error (%d) encountered", e.Code) - case 6: - return "Bad Xpath" - case 7: - return "Object not found" - case 8: - return "Object not unique" - case 10: - return "Reference count not zero" - case 12: - return "Invalid object" - case 14: - return "Operation not possible" - case 15: - return "Operation denied" - case 16: - return "Unauthorized" - case 17: - return "Invalid command" - case 18: - return "Malformed command" - case 0, 19, 20: - return "" - case 22: - return "Session timed out" - default: - return fmt.Sprintf("(%d) Unknown failure code, operation failed", e.Code) - } -} diff --git a/assets/errors/panos_test.go b/assets/errors/panos_test.go deleted file mode 100644 index 70f07dcf..00000000 --- a/assets/errors/panos_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package errors - -import ( - "fmt" - "strings" - "testing" -) - -func TestGetSingularMissingObjectIsError(t *testing.T) { - data := `` - - err := Parse([]byte(data)) - if err == nil { - t.Errorf("Error is nil") - } else { - e2, ok := err.(Panos) - if !ok { - t.Errorf("Not a panos error") - } else if !e2.ObjectNotFound() { - t.Errorf("Not an object not found error") - } - } -} - -func TestShowSingularMissingObjectIsError(t *testing.T) { - data := `No such node` - - err := Parse([]byte(data)) - if err == nil { - t.Errorf("Error is nil") - } else { - e2, ok := err.(Panos) - if !ok { - t.Errorf("Not a panos error") - } else if e2.Msg != "No such node" { - t.Errorf("Incorrect msg: %s", e2.Msg) - } - } -} - -func TestMultilineErrorMessage(t *testing.T) { - expected := "HTTP method must be GET" - data := fmt.Sprintf(` server -> first server is invalid. %s, Username/Password must not be empty when Tag Distribution is chosen]]> server is invalid]]>`, expected) - - err := Parse([]byte(data)) - if err == nil { - t.Errorf("Error is nil") - } else { - e2, ok := err.(Panos) - if !ok { - t.Errorf("Not a panos error") - } else if !strings.Contains(e2.Msg, expected) { - t.Errorf("Does not contain the expected substring: %s", e2.Msg) - } - } -} - -func TestFailedExportErrorMessage(t *testing.T) { - expected := `Parameter "format" is required while exporting certificate` - data := `Parameter "format" is required while exporting certificate` - - err := Parse([]byte(data)) - if err == nil { - t.Errorf("Error is nil") - } else { - e2, ok := err.(Panos) - if !ok { - t.Errorf("Not a pnos error") - } else if !strings.Contains(e2.Msg, expected) { - t.Errorf("Does not contain the expected substring: %s", e2.Msg) - } - } -} diff --git a/assets/util/actioner.go b/assets/util/actioner.go deleted file mode 100644 index 0b516835..00000000 --- a/assets/util/actioner.go +++ /dev/null @@ -1,5 +0,0 @@ -package util - -type Actioner interface { - Action() string -} diff --git a/assets/util/bulk_element.go b/assets/util/bulk_element.go deleted file mode 100644 index 5a129757..00000000 --- a/assets/util/bulk_element.go +++ /dev/null @@ -1,19 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// BulkElement is a generic bulk container for bulk operations. -type BulkElement struct { - XMLName xml.Name - Data []interface{} -} - -// Config returns an interface to be Marshaled. -func (o BulkElement) Config() interface{} { - if len(o.Data) == 1 { - return o.Data[0] - } - return o -} diff --git a/assets/util/comparison.go b/assets/util/comparison.go deleted file mode 100644 index e5c164b7..00000000 --- a/assets/util/comparison.go +++ /dev/null @@ -1,76 +0,0 @@ -package util - -func UnorderedListsMatch(a, b []string) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } else if len(a) != len(b) { - return false - } - - for _, x := range a { - var found bool - for _, y := range b { - if x == y { - found = true - break - } - } - if !found { - return false - } - } - - return true -} - -func OrderedListsMatch(a, b []string) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } else if len(a) != len(b) { - return false - } - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - -func TargetsMatch(a, b map[string][]string) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } else if len(a) != len(b) { - return false - } - - for key := range a { - if !UnorderedListsMatch(a[key], b[key]) { - return false - } - } - - return true -} - -func OptionalStringsMatch(a, b *string) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } - - return *a == *b -} - -func StringsMatch(a, b string) bool { - return a == b -} diff --git a/assets/util/const.go b/assets/util/const.go deleted file mode 100644 index c5a6a729..00000000 --- a/assets/util/const.go +++ /dev/null @@ -1,39 +0,0 @@ -package util - -// Rulebase constants for various policies. -const ( - Rulebase = "rulebase" - PreRulebase = "pre-rulebase" - PostRulebase = "post-rulebase" -) - -// Valid values to use for VsysImport() or VsysUnimport(). -const ( - InterfaceImport = "interface" - VirtualRouterImport = "virtual-router" - VirtualWireImport = "virtual-wire" - VlanImport = "vlan" -) - -// These constants are valid move locations to pass to various movement -// functions (aka - policy management). -const ( - MoveSkip = iota - MoveBefore - MoveDirectlyBefore - MoveAfter - MoveDirectlyAfter - MoveTop - MoveBottom -) - -// Valid values to use for any function expecting a pango query type `qt`. -const ( - Get = "get" - Show = "show" -) - -// PanosTimeWithoutTimezoneFormat is a time (missing the timezone) that PAN-OS -// will give sometimes. Combining this with `Clock()` to get a usable time. -// report that does not contain -const PanosTimeWithoutTimezoneFormat = "2006/01/02 15:04:05" diff --git a/assets/util/copy.go b/assets/util/copy.go deleted file mode 100644 index 6a7765cd..00000000 --- a/assets/util/copy.go +++ /dev/null @@ -1,31 +0,0 @@ -package util - -func CopyStringSlice(v []string) []string { - if v == nil { - return nil - } - - ans := make([]string, len(v)) - copy(ans, v) - - return ans -} - -func CopyTargets(v map[string][]string) map[string][]string { - if v == nil { - return nil - } - - ans := make(map[string][]string) - for key, oval := range v { - if oval == nil { - ans[key] = nil - } else { - val := make([]string, len(oval)) - copy(val, oval) - ans[key] = val - } - } - - return ans -} diff --git a/assets/util/elementer.go b/assets/util/elementer.go deleted file mode 100644 index 72688d67..00000000 --- a/assets/util/elementer.go +++ /dev/null @@ -1,6 +0,0 @@ -package util - -// Elementer is an interface for commits. -type Elementer interface { - Element() interface{} -} diff --git a/assets/util/entry.go b/assets/util/entry.go deleted file mode 100644 index 0c1ea4c9..00000000 --- a/assets/util/entry.go +++ /dev/null @@ -1,67 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// EntryType defines an entry config node used for sending and receiving XML -// from PAN-OS. -type EntryType struct { - Entries []Entry `xml:"entry"` -} - -// Entry is a standalone entry struct. -type Entry struct { - XMLName xml.Name `xml:"entry"` - Value string `xml:"name,attr"` -} - -// EntToStr normalizes an EntryType pointer into a list of strings. -func EntToStr(e *EntryType) []string { - if e == nil { - return nil - } - - ans := make([]string, len(e.Entries)) - for i := range e.Entries { - ans[i] = e.Entries[i].Value - } - - return ans -} - -// StrToEnt converts a list of strings into an EntryType pointer. -func StrToEnt(e []string) *EntryType { - if e == nil { - return nil - } - - ans := make([]Entry, len(e)) - for i := range e { - ans[i] = Entry{Value: e[i]} - } - - return &EntryType{ans} -} - -// EntToOneStr normalizes an EntryType pointer for a max_items=1 XML node -// into a string. -func EntToOneStr(e *EntryType) string { - if e == nil || len(e.Entries) == 0 { - return "" - } - - return e.Entries[0].Value -} - -// OneStrToEnt converts a string into an EntryType pointer for a max_items=1 -// XML node. -func OneStrToEnt(e string) *EntryType { - if e == "" { - return nil - } - - return &EntryType{[]Entry{ - {Value: e}, - }} -} diff --git a/assets/util/hitcount.go b/assets/util/hitcount.go deleted file mode 100644 index d10e7f77..00000000 --- a/assets/util/hitcount.go +++ /dev/null @@ -1,67 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// NewHitCountRequest returns a new hit count request struct. -// -// If the rules param is nil, then the hit count for all rules is returned. -func NewHitCountRequest(rulebase, vsys string, rules []string) interface{} { - req := hcReq{ - Vsys: hcReqVsys{ - Name: vsys, - Rulebase: hcReqRulebase{ - Name: rulebase, - Rules: hcReqRules{ - List: StrToMem(rules), - }, - }, - }, - } - - if req.Vsys.Rulebase.Rules.List == nil { - s := "" - req.Vsys.Rulebase.Rules.All = &s - } - - return req -} - -// HitCountResponse is the hit count response struct. -type HitCountResponse struct { - XMLName xml.Name `xml:"response"` - Results []HitCount `xml:"result>rule-hit-count>vsys>entry>rule-base>entry>rules>entry"` -} - -// HitCount is the hit count data for a specific rule. -type HitCount struct { - Name string `xml:"name,attr"` - Latest string `xml:"latest"` - HitCount uint `xml:"hit-count"` - LastHitTimestamp int `xml:"last-hit-timestamp"` - LastResetTimestamp int `xml:"last-reset-timestamp"` - FirstHitTimestamp int `xml:"first-hit-timestamp"` - RuleCreationTimestamp int `xml:"rule-creation-timestamp"` - RuleModificationTimestamp int `xml:"rule-modification-timestamp"` -} - -type hcReq struct { - XMLName xml.Name `xml:"show"` - Vsys hcReqVsys `xml:"rule-hit-count>vsys>vsys-name>entry"` -} - -type hcReqVsys struct { - Name string `xml:"name,attr"` - Rulebase hcReqRulebase `xml:"rule-base>entry"` -} - -type hcReqRulebase struct { - Name string `xml:"name,attr"` - Rules hcReqRules `xml:"rules"` -} - -type hcReqRules struct { - All *string `xml:"all"` - List *MemberType `xml:"list"` -} diff --git a/assets/util/jobs.go b/assets/util/jobs.go deleted file mode 100644 index e3b9ca63..00000000 --- a/assets/util/jobs.go +++ /dev/null @@ -1,71 +0,0 @@ -package util - -import ( - "encoding/xml" - "strconv" - "strings" -) - -// JobResponse parses a XML response that includes a job ID. -type JobResponse struct { - XMLName xml.Name `xml:"response"` - Id uint `xml:"result>job"` -} - -// BasicJob is a struct for parsing minimal information about a submitted -// job to PANOS. -type BasicJob struct { - XMLName xml.Name `xml:"response"` - Result string `xml:"result>job>result"` - Progress uint `xml:"-"` - Details BasicJobDetails `xml:"result>job>details"` - Devices []devJob `xml:"result>job>devices>entry"` - Status string `xml:"result>job>status"` // For log retrieval jobs. - ProgressRaw string `xml:"result>job>progress"` -} - -func (o *BasicJob) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - type localBasicJob BasicJob - var ans localBasicJob - if err := d.DecodeElement(&ans, &start); err != nil { - return err - } - - val, err := strconv.ParseUint(strings.TrimSpace(ans.ProgressRaw), 10, 32) - if err == nil { - ans.Progress = uint(val) - } - - *o = BasicJob(ans) - return nil -} - -type BasicJobDetails struct { - Lines []LineOrCdata `xml:"line"` -} - -func (o *BasicJobDetails) String() string { - ans := make([]string, 0, len(o.Lines)) - - for _, line := range o.Lines { - if line.Cdata != nil { - ans = append(ans, strings.TrimSpace(*line.Cdata)) - } else if line.Text != nil { - ans = append(ans, *line.Text) - } else { - ans = append(ans, "huh") - } - } - - return strings.Join(ans, " | ") -} - -type LineOrCdata struct { - Cdata *string `xml:",cdata"` - Text *string `xml:",chardata"` -} - -type devJob struct { - Serial string `xml:"serial-no"` - Result string `xml:"result"` -} diff --git a/assets/util/license.go b/assets/util/license.go deleted file mode 100644 index 5bd7f44f..00000000 --- a/assets/util/license.go +++ /dev/null @@ -1,17 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// License defines a license entry. -type License struct { - XMLName xml.Name `xml:"entry"` - Feature string `xml:"feature"` - Description string `xml:"description"` - Serial string `xml:"serial"` - Issued string `xml:"issued"` - Expires string `xml:"expires"` - Expired string `xml:"expired"` - AuthCode string `xml:"authcode"` -} diff --git a/assets/util/lock.go b/assets/util/lock.go deleted file mode 100644 index 1e819c2f..00000000 --- a/assets/util/lock.go +++ /dev/null @@ -1,15 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// Lock represents either a config lock or a commit lock. -type Lock struct { - XMLName xml.Name `xml:"entry"` - Owner string `xml:"name,attr"` - Name string `xml:"name"` - Type string `xml:"type"` - LoggedIn string `xml:"loggedin"` - Comment CdataText `xml:"comment"` -} diff --git a/assets/util/member.go b/assets/util/member.go deleted file mode 100644 index 658cbb0c..00000000 --- a/assets/util/member.go +++ /dev/null @@ -1,68 +0,0 @@ -package util - -import ( - "encoding/xml" -) - -// MemberType defines a member config node used for sending and receiving XML -// from PAN-OS. -type MemberType struct { - Members []Member `xml:"member"` -} - -// Member defines a member config node used for sending and receiving XML -// from PANOS. -type Member struct { - XMLName xml.Name `xml:"member"` - Value string `xml:",chardata"` -} - -// MemToStr normalizes a MemberType pointer into a list of strings. -func MemToStr(e *MemberType) []string { - if e == nil { - return nil - } - - ans := make([]string, len(e.Members)) - for i := range e.Members { - ans[i] = e.Members[i].Value - } - - return ans -} - -// StrToMem converts a list of strings into a MemberType pointer. -func StrToMem(e []string) *MemberType { - if e == nil { - return nil - } - - ans := make([]Member, len(e)) - for i := range e { - ans[i] = Member{Value: e[i]} - } - - return &MemberType{ans} -} - -// MemToOneStr normalizes a MemberType pointer for a max_items=1 XML node -// into a string. -func MemToOneStr(e *MemberType) string { - if e == nil || len(e.Members) == 0 { - return "" - } - - return e.Members[0].Value -} - -// OneStrToMem converts a string into a MemberType pointer for a max_items=1 -// XML node. -func OneStrToMem(e string) *MemberType { - if e == "" { - return nil - } - - return &MemberType{[]Member{ - {Value: e}, - }} -} diff --git a/assets/util/pangoclient.go b/assets/util/pangoclient.go deleted file mode 100644 index 0f2a3338..00000000 --- a/assets/util/pangoclient.go +++ /dev/null @@ -1,21 +0,0 @@ -package util - -import ( - "context" - "net/http" - "net/url" - - "github.com/PaloAltoNetworks/pango/plugin" - "github.com/PaloAltoNetworks/pango/version" - "github.com/PaloAltoNetworks/pango/xmlapi" -) - -type PangoClient interface { - Versioning() version.Number - GetTarget() string - Plugins() []plugin.Info - MultiConfig(context.Context, *xmlapi.MultiConfig, bool, url.Values) ([]byte, *http.Response, *xmlapi.MultiConfigResponse, error) - Communicate(context.Context, PangoCommand, bool, any) ([]byte, *http.Response, error) - CommunicateFile(context.Context, string, string, string, url.Values, bool, any) ([]byte, *http.Response, error) - ReadFromConfig(context.Context, []string, bool, any) ([]byte, error) -} diff --git a/assets/util/pangocommand.go b/assets/util/pangocommand.go deleted file mode 100644 index 362c2f3b..00000000 --- a/assets/util/pangocommand.go +++ /dev/null @@ -1,9 +0,0 @@ -package util - -import ( - "net/url" -) - -type PangoCommand interface { - AsUrlValues() (url.Values, error) -} diff --git a/assets/util/retriever.go b/assets/util/retriever.go deleted file mode 100644 index 4d4c1459..00000000 --- a/assets/util/retriever.go +++ /dev/null @@ -1,5 +0,0 @@ -package util - -// Retriever is a type that is intended to act as a stand-in for using -// either the Get or Show pango Client functions. -type Retriever func(interface{}, interface{}, interface{}) ([]byte, error) diff --git a/assets/util/util.go b/assets/util/util.go deleted file mode 100644 index 2a21a3e3..00000000 --- a/assets/util/util.go +++ /dev/null @@ -1,282 +0,0 @@ -// Package util contains various shared structs and functions used across -// the pango package. -package util - -import ( - "bytes" - "encoding/xml" - "fmt" - "regexp" - "strings" -) - -// VsysEntryType defines an entry config node with vsys entries underneath. -type VsysEntryType struct { - Entries []VsysEntry `xml:"entry"` -} - -// VsysEntry defines the "vsys" xpath node under a VsysEntryType config node. -type VsysEntry struct { - XMLName xml.Name `xml:"entry"` - Serial string `xml:"name,attr"` - Vsys *EntryType `xml:"vsys"` -} - -// VsysEntToMap normalizes a VsysEntryType pointer into a map. -func VsysEntToMap(ve *VsysEntryType) map[string][]string { - if ve == nil { - return nil - } - - ans := make(map[string][]string) - for i := range ve.Entries { - ans[ve.Entries[i].Serial] = EntToStr(ve.Entries[i].Vsys) - } - - return ans -} - -// MapToVsysEnt converts a map into a VsysEntryType pointer. -// -// This struct is used for "Target" information on Panorama when dealing with -// various policies. Maps are unordered, but FWICT Panorama doesn't seem to -// order anything anyways when doing things in the GUI, so hopefully this is -// ok...? -func MapToVsysEnt(e map[string][]string) *VsysEntryType { - if len(e) == 0 { - return nil - } - - i := 0 - ve := make([]VsysEntry, len(e)) - for key := range e { - ve[i].Serial = key - ve[i].Vsys = StrToEnt(e[key]) - i++ - } - - return &VsysEntryType{ve} -} - -// YesNo returns "yes" on true, "no" on false. -func YesNo(v bool) string { - if v { - return "yes" - } - return "no" -} - -// AsBool returns true on yes, else false. -func AsBool(val string) bool { - if val == "yes" { - return true - } - return false -} - -// AsXpath makes an xpath out of the given interface. -func AsXpath(i interface{}) string { - switch val := i.(type) { - case string: - return val - case []string: - return fmt.Sprintf("/%s", strings.Join(val, "/")) - default: - return "" - } -} - -// AsEntryXpath returns the given values as an entry xpath segment. -func AsEntryXpath(vals []string) string { - if len(vals) == 0 || (len(vals) == 1 && vals[0] == "") { - return "entry" - } - - var buf bytes.Buffer - - buf.WriteString("entry[") - for i := range vals { - if i != 0 { - buf.WriteString(" or ") - } - buf.WriteString("@name='") - buf.WriteString(vals[i]) - buf.WriteString("'") - } - buf.WriteString("]") - - return buf.String() -} - -// AsMemberXpath returns the given values as a member xpath segment. -func AsMemberXpath(vals []string) string { - var buf bytes.Buffer - - buf.WriteString("member[") - for i := range vals { - if i != 0 { - buf.WriteString(" or ") - } - buf.WriteString("text()='") - buf.WriteString(vals[i]) - buf.WriteString("'") - } - - buf.WriteString("]") - - return buf.String() -} - -// TemplateXpathPrefix returns the template xpath prefix of the given template name. -func TemplateXpathPrefix(tmpl, ts string) []string { - if tmpl != "" { - return []string{ - "config", - "devices", - AsEntryXpath([]string{"localhost.localdomain"}), - "template", - AsEntryXpath([]string{tmpl}), - } - } - - return []string{ - "config", - "devices", - AsEntryXpath([]string{"localhost.localdomain"}), - "template-stack", - AsEntryXpath([]string{ts}), - } -} - -// DeviceGroupXpathPrefix returns a device group xpath prefix. -// If the device group is empty, then the default is "shared". -func DeviceGroupXpathPrefix(dg string) []string { - if dg == "" || dg == "shared" { - return []string{"config", "shared"} - } - - return []string{ - "config", - "devices", - AsEntryXpath([]string{"localhost.localdomain"}), - "device-group", - AsEntryXpath([]string{dg}), - } -} - -// VsysXpathPrefix returns a vsys xpath prefix. -func VsysXpathPrefix(vsys string) []string { - if vsys == "" { - vsys = "vsys1" - } else if vsys == "shared" { - return []string{"config", "shared"} - } - - return []string{ - "config", - "devices", - AsEntryXpath([]string{"localhost.localdomain"}), - "vsys", - AsEntryXpath([]string{vsys}), - } -} - -// PanoramaXpathPrefix returns the panorama xpath prefix. -func PanoramaXpathPrefix() []string { - return []string{ - "config", - "panorama", - } -} - -// StripPanosPackaging removes the response / result and an optional third -// containing XML tag from the given byte slice. -func StripPanosPackaging(input []byte, tag string) []byte { - var index int - gt := []byte(">") - lt := []byte("<") - - // Remove response. - index = bytes.Index(input, gt) - ans := input[index+1:] - index = bytes.LastIndex(ans, lt) - ans = ans[:index] - - // Remove result. - index = bytes.Index(ans, gt) - ans = ans[index+1:] - index = bytes.LastIndex(ans, lt) - ans = ans[:index] - - ans = bytes.TrimSpace(ans) - - if tag != "" { - if bytes.HasPrefix(ans, []byte("<"+tag+" ")) || bytes.HasPrefix(ans, []byte("<"+tag+">")) { - index = bytes.Index(ans, gt) - ans = ans[index+1:] - if len(ans) > 0 { - index = bytes.LastIndex(ans, lt) - ans = ans[:index] - ans = bytes.TrimSpace(ans) - } - } - } - - return ans -} - -// CdataText is for getting CDATA contents of XML docs. -type CdataText struct { - Text string `xml:",cdata"` -} - -// RawXml is what allows the use of Edit commands on a XPATH without -// truncating any other child objects that may be attached to it. -type RawXml struct { - Text string `xml:",innerxml"` -} - -// CleanRawXml removes extra XML attributes from RawXml objects without -// requiring us to have to parse everything. -func CleanRawXml(v string) string { - re := regexp.MustCompile(` admin="\S+" dirtyId="\d+" time="\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}"`) - return re.ReplaceAllString(v, "") -} - -// ValidMovement returns if the movement constant is valid or not. -func ValidMovement(v int) bool { - switch v { - case MoveSkip, MoveBefore, MoveDirectlyBefore, MoveAfter, MoveDirectlyAfter, MoveTop, MoveBottom: - return true - } - - return false -} - -// RelativeMovement returns if the movement constant is a relative movement. -func RelativeMovement(v int) bool { - switch v { - case MoveBefore, MoveDirectlyBefore, MoveAfter, MoveDirectlyAfter: - return true - } - - return false -} - -// ValidateRulebase validates the device group and rulebase pairing for -// Panorama policies. -func ValidateRulebase(dg, base string) error { - switch base { - case "": - return fmt.Errorf("rulebase must be specified") - case Rulebase: - if dg != "shared" { - return fmt.Errorf("rulebase %q requires \"shared\" device group", base) - } - case PreRulebase, PostRulebase: - default: - return fmt.Errorf("unknown rulebase %q", base) - } - - return nil -} diff --git a/assets/util/util_test.go b/assets/util/util_test.go deleted file mode 100644 index 08126d0d..00000000 --- a/assets/util/util_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package util - -import ( - "bytes" - "fmt" - "testing" -) - -func TestMemToStrNil(t *testing.T) { - r := MemToStr(nil) - if r != nil { - t.Fail() - } -} - -func TestEntToStrNil(t *testing.T) { - r := EntToStr(nil) - if r != nil { - t.Fail() - } -} - -func TestStrToMem(t *testing.T) { - v := []string{"one", "two"} - r := StrToMem(v) - if r == nil { - t.Fail() - } else if len(v) != len(r.Members) { - t.Fail() - } else { - for i := range v { - if v[i] != r.Members[i].Value { - t.Fail() - break - } - } - } -} - -func TestStrToEnt(t *testing.T) { - v := []string{"one", "two"} - r := StrToEnt(v) - if r == nil { - t.Fail() - } else if len(v) != len(r.Entries) { - t.Fail() - } else { - for i := range v { - if v[i] != r.Entries[i].Value { - t.Fail() - break - } - } - } -} - -func BenchmarkStrToMem(b *testing.B) { - v := []string{"one", "two", "three", "four", "five"} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = StrToMem(v) - } -} - -func BenchmarkMemToStr(b *testing.B) { - m := &MemberType{[]Member{ - {Value: "one"}, - {Value: "two"}, - {Value: "three"}, - {Value: "four"}, - {Value: "five"}, - }} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = MemToStr(m) - } -} - -func BenchmarkStrToEnt(b *testing.B) { - v := []string{"one", "two", "three", "four", "five"} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = StrToEnt(v) - } -} - -func BenchmarkEntToStr(b *testing.B) { - v := &EntryType{[]Entry{ - {Value: "one"}, - {Value: "two"}, - {Value: "three"}, - {Value: "four"}, - {Value: "five"}, - }} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = EntToStr(v) - } -} - -func BenchmarkAsXpath(b *testing.B) { - p := []string{ - "config", - "devices", - AsEntryXpath([]string{"localhost.localdomain"}), - "vsys", - AsEntryXpath([]string{"vsys1"}), - "import", - "network", - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = AsXpath(p) - } -} - -func BenchmarkAsEntryXpathMultiple(b *testing.B) { - v := []string{"one", "two", "three"} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _ = AsEntryXpath(v) - } -} - -func TestAsXpath(t *testing.T) { - testCases := []struct { - i interface{} - r string - }{ - {"/one/two", "/one/two"}, - {[]string{"one", "two"}, "/one/two"}, - {42, ""}, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("%v to %s", tc.i, tc.r), func(t *testing.T) { - if AsXpath(tc.i) != tc.r { - t.Fail() - } - }) - } -} - -func TestAsEntryXpath(t *testing.T) { - testCases := []struct { - v []string - r string - }{ - {[]string{"one"}, "entry[@name='one']"}, - {[]string{"one", "two"}, "entry[@name='one' or @name='two']"}, - {nil, "entry"}, - } - - for _, tc := range testCases { - t.Run(tc.r, func(t *testing.T) { - if AsEntryXpath(tc.v) != tc.r { - t.Fail() - } - }) - } -} - -func TestAsMemberXpath(t *testing.T) { - testCases := []struct { - v []string - r string - }{ - {[]string{"one"}, "member[text()='one']"}, - {[]string{"one", "two"}, "member[text()='one' or text()='two']"}, - {nil, "member[]"}, - } - - for _, tc := range testCases { - t.Run(tc.r, func(t *testing.T) { - if AsMemberXpath(tc.v) != tc.r { - t.Fail() - } - }) - } -} - -func TestCleanRawXml(t *testing.T) { - v := `hi` - if CleanRawXml(v) != "hi" { - t.Fail() - } -} - -func TestStripPanosPackagingNoTag(t *testing.T) { - expected := "" - input := fmt.Sprintf("%s", expected) - - ans := StripPanosPackaging([]byte(input), "") - if !bytes.Equal([]byte(expected), ans) { - t.Errorf("Expected %q, got %q", expected, ans) - } -} - -func TestStripPanosPackagingWithTag(t *testing.T) { - expected := "" - input := fmt.Sprintf("%s", expected) - - ans := StripPanosPackaging([]byte(input), "outer") - if !bytes.Equal([]byte(expected), ans) { - t.Errorf("Expected %q, got %q", expected, ans) - } -} - -func TestStripPanosPackagingNoResult(t *testing.T) { - input := ` - -` - - ans := StripPanosPackaging([]byte(input), "interface") - if len(ans) != 0 { - t.Errorf("Expected empty string, got %q", ans) - } -} diff --git a/assets/util/xapiclient.go b/assets/util/xapiclient.go deleted file mode 100644 index 992d21b7..00000000 --- a/assets/util/xapiclient.go +++ /dev/null @@ -1,52 +0,0 @@ -package util - -import ( - "time" - - "github.com/PaloAltoNetworks/pango/plugin" - "github.com/PaloAltoNetworks/pango/version" -) - -// XapiClient is the interface that describes an pango.Client. -type XapiClient interface { - String() string - Versioning() version.Number - Plugins() []plugin.Info - - // Logging functions. - LogAction(string, ...interface{}) - LogQuery(string, ...interface{}) - LogOp(string, ...interface{}) - LogUid(string, ...interface{}) - LogLog(string, ...interface{}) - LogExport(string, ...interface{}) - LogImport(string, ...interface{}) - - // PAN-OS API calls. - Op(interface{}, string, interface{}, interface{}) ([]byte, error) - Show(interface{}, interface{}, interface{}) ([]byte, error) - Get(interface{}, interface{}, interface{}) ([]byte, error) - Delete(interface{}, interface{}, interface{}) ([]byte, error) - Set(interface{}, interface{}, interface{}, interface{}) ([]byte, error) - Edit(interface{}, interface{}, interface{}, interface{}) ([]byte, error) - Move(interface{}, string, string, interface{}, interface{}) ([]byte, error) - Log(string, string, string, string, int, int, interface{}, interface{}) ([]byte, error) - Export(string, time.Duration, interface{}, interface{}) (string, []byte, error) - Import(string, string, string, string, time.Duration, interface{}, interface{}) ([]byte, error) - Commit(interface{}, string, interface{}) (uint, []byte, error) - Uid(interface{}, string, interface{}, interface{}) ([]byte, error) - - // Vsys importables. - VsysImport(string, string, string, string, []string) error - VsysUnimport(string, string, string, []string) error - - // Extras. - EntryListUsing(Retriever, []string) ([]string, error) - MemberListUsing(Retriever, []string) ([]string, error) - RequestPasswordHash(string) (string, error) - WaitForJob(uint, time.Duration, interface{}, interface{}) error - WaitForLogs(uint, time.Duration, time.Duration, interface{}) ([]byte, error) - Clock() (time.Time, error) - PositionFirstEntity(int, string, string, []string, []string) error - ConfigTree() *XmlNode -} diff --git a/assets/util/xmlnode.go b/assets/util/xmlnode.go deleted file mode 100644 index f6f3410d..00000000 --- a/assets/util/xmlnode.go +++ /dev/null @@ -1,54 +0,0 @@ -package util - -import ( - "encoding/xml" - "strings" -) - -const ( - entryPrefix = "entry[@name='" - entrySuffix = "']" -) - -// XmlNode is a generic XML node. -type XmlNode struct { - XMLName xml.Name - Attributes []xml.Attr `xml:",any,attr"` - Text []byte `xml:",innerxml"` - Nodes []XmlNode `xml:",any"` -} - -// FindXmlNodeInTree finds a given path in the specified XmlNode tree. -func FindXmlNodeInTree(path []string, elm *XmlNode) *XmlNode { - if len(path) == 0 { - return elm - } - - if elm == nil { - return elm - } - - tag := path[0] - path = path[1:] - var name string - if strings.HasPrefix(tag, entryPrefix) { - name = strings.TrimSuffix(strings.TrimPrefix(tag, entryPrefix), entrySuffix) - tag = "entry" - } - - for _, x := range elm.Nodes { - if x.XMLName.Local == tag { - if name == "" { - return FindXmlNodeInTree(path, &x) - } else { - for _, atr := range x.Attributes { - if atr.Name.Local == "name" && atr.Value == name { - return FindXmlNodeInTree(path, &x) - } - } - } - } - } - - return nil -} diff --git a/assets/version/version.go b/assets/version/version.go deleted file mode 100644 index b982d9d1..00000000 --- a/assets/version/version.go +++ /dev/null @@ -1,72 +0,0 @@ -// Package version contains a version number struct that pango uses to make -// decisions on the specific structs to use when sending XML to the PANOS -// device. -package version - -import ( - "fmt" - "strconv" - "strings" -) - -// Number is the version number struct. -type Number struct { - Major, Minor, Patch int - Suffix string -} - -// Gte tests if this version number is greater than or equal to the argument. -func (v Number) Gte(o Number) 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 Number) String() string { - if v.Suffix == "" { - 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.Suffix) - } -} - -// New returns a version number from the given string. -func New(version string) (Number, error) { - parts := strings.Split(version, ".")[:3] - - major, err := strconv.Atoi(parts[0]) - if err != nil { - return Number{}, fmt.Errorf("Major %s is not a number: %s", parts[0], err) - } - - minor, err := strconv.Atoi(parts[1]) - if err != nil { - return Number{}, fmt.Errorf("Minor %s is not a number: %s", parts[0], err) - } - - var patch_str string - var suffix string - patch_parts := strings.Split(parts[2], "-") - if len(patch_parts) == 1 { - patch_str = parts[2] - suffix = "" - } else if len(patch_parts) == 2 { - patch_str = patch_parts[0] - suffix = patch_parts[1] - } else { - return Number{}, fmt.Errorf("Patch %s is not formatted as expected", parts[2]) - } - patch, err := strconv.Atoi(patch_str) - if err != nil { - return Number{}, fmt.Errorf("Patch %s is not a number: %s", patch_str, err) - } - - return Number{major, minor, patch, suffix}, nil -} diff --git a/assets/version/version_test.go b/assets/version/version_test.go deleted file mode 100644 index 547d5aa3..00000000 --- a/assets/version/version_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package version - -import ( - "fmt" - "testing" -) - -func TestNew(t *testing.T) { - testCases := []struct { - s string - r bool - a, b, c int - d string - }{ - {"1.2.3", false, 1, 2, 3, ""}, - {"1.2.3-h4", false, 1, 2, 3, "h4"}, - {"12.34.56-78h", false, 12, 34, 56, "78h"}, - {"a.2.3", true, 0, 0, 0, ""}, - {"1.b.3", true, 0, 0, 0, ""}, - {"1.2.c", true, 0, 0, 0, ""}, - {"1.2.3h4", true, 0, 0, 0, ""}, - {"9.0.3.xfr", false, 9, 0, 3, ""}, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s should error %t", tc.s, tc.r), func(t *testing.T) { - v, err := New(tc.s) - if (err != nil) != tc.r || v.Major != tc.a || v.Minor != tc.b || v.Patch != tc.c || v.Suffix != tc.d { - t.Fail() - } - }) - } -} - -func TestStringer(t *testing.T) { - testCases := []struct { - a, b, c int - d, want string - }{ - {1, 2, 3, "", "1.2.3"}, - {1, 2, 3, "h4", "1.2.3-h4"}, - {12, 34, 56, "h78", "12.34.56-h78"}, - } - - for _, tc := range testCases { - t.Run(tc.want, func(t *testing.T) { - v := Number{tc.a, tc.b, tc.c, tc.d} - if v.String() != tc.want { - t.Fail() - } - }) - } -} - -func TestGte(t *testing.T) { - testCases := []struct { - a, b, c int - r bool - }{ - {1, 1, 1, true}, - {1, 1, 2, true}, - {1, 1, 3, true}, - {1, 2, 1, true}, - {1, 2, 2, true}, - {1, 2, 3, true}, - {1, 3, 1, true}, - {1, 3, 2, true}, - {1, 3, 3, true}, - {2, 1, 1, true}, - {2, 1, 2, true}, - {2, 1, 3, true}, - {2, 2, 1, true}, - {2, 2, 2, true}, - {2, 2, 3, false}, - {2, 3, 1, false}, - {2, 3, 2, false}, - {2, 3, 3, false}, - {3, 1, 1, false}, - {3, 1, 2, false}, - {3, 1, 3, false}, - {3, 2, 1, false}, - {3, 2, 2, false}, - {3, 2, 3, false}, - {3, 3, 1, false}, - {3, 3, 2, false}, - {3, 3, 3, false}, - } - v1 := Number{2, 2, 2, ""} - - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s >= %d.%d.%d == %t", v1, tc.a, tc.b, tc.c, tc.r), func(t *testing.T) { - r := v1.Gte(Number{tc.a, tc.b, tc.c, ""}) - if r != tc.r { - t.Fail() - } - }) - } -} - -func BenchmarkGteMajor(b *testing.B) { - v1 := Number{5, 5, 5, ""} - v2 := Number{6, 5, 5, ""} - - for i := 0; i < b.N; i++ { - _ = v1.Gte(v2) - } -} - -func BenchmarkGtePatch(b *testing.B) { - v1 := Number{5, 5, 5, ""} - v2 := Number{5, 5, 6, ""} - - for i := 0; i < b.N; i++ { - _ = v1.Gte(v2) - } -} - -func BenchmarkNew(b *testing.B) { - s := "7.1.12-h4" - - for i := 0; i < b.N; i++ { - _, _ = New(s) - } -} diff --git a/cmd/mktp/config.yaml b/cmd/mktp/config.yaml index 95ff5e40..ff60b01c 100644 --- a/cmd/mktp/config.yaml +++ b/cmd/mktp/config.yaml @@ -2,21 +2,21 @@ output: go_sdk: "../generated/pango" terraform_provider: "../generated/terraform-provider-panos" assets: - util_package: - source: "assets/util" - target: - go_sdk: true - terraform_provider: false - destination: "util" - errors_package: - source: "assets/errors" - target: - go_sdk: true - terraform_provider: false - destination: "errors" - version_package: - source: "assets/version" - target: - go_sdk: true - terraform_provider: false - destination: "version" +# util_package: +# source: "assets/util" +# target: +# go_sdk: true +# terraform_provider: false +# destination: "util" +# errors_package: +# source: "assets/errors" +# target: +# go_sdk: true +# terraform_provider: false +# destination: "errors" +# version_package: +# source: "assets/version" +# target: +# go_sdk: true +# terraform_provider: false +# destination: "version" From dee185456f9bb4554972c37e20bea758c275408f Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 8 Mar 2024 09:29:32 +0100 Subject: [PATCH 32/32] remove files copied from pango --- assets/generic/xml.go | 40 ------------- assets/generic/xml_test.go | 117 ------------------------------------- 2 files changed, 157 deletions(-) delete mode 100644 assets/generic/xml.go delete mode 100644 assets/generic/xml_test.go diff --git a/assets/generic/xml.go b/assets/generic/xml.go deleted file mode 100644 index cae21261..00000000 --- a/assets/generic/xml.go +++ /dev/null @@ -1,40 +0,0 @@ -package generic - -import ( - "encoding/xml" - "strings" -) - -// Xml is a generic catch-all for parsing XML returned from PAN-OS. -type Xml struct { - XMLName xml.Name - Name *string `xml:"name,attr,omitempty"` - Uuid *string `xml:"uuid,attr,omitempty"` - DetailedVersion *string `xml:"detail-version,attr,omitempty"` - Text []byte `xml:",chardata"` - Nodes []Xml `xml:",any"` - - // TrimmedText contains the trimmed value of Text. Note that since this could - // very well be trimming legitimate spacing that the text field would otherwise - // contain, refering to this field for anything other than debugging purposes is - // probably not a good idea. - TrimmedText *string `xml:"-"` -} - -func (x *Xml) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - type local Xml - var ans local - if err := d.DecodeElement(&ans, &start); err != nil { - return err - } - - if len(ans.Text) != 0 { - v := strings.TrimSpace(string(ans.Text)) - if v != "" { - ans.TrimmedText = &v - } - } - - *x = Xml(ans) - return nil -} diff --git a/assets/generic/xml_test.go b/assets/generic/xml_test.go deleted file mode 100644 index b01f5eef..00000000 --- a/assets/generic/xml_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package generic - -import ( - "encoding/xml" - "strings" - "testing" -) - -var ( - TestXml = []byte(` - - some text - - - - - -`) -) - -func TestUnmarshalDoesNotReturnError(t *testing.T) { - var x Xml - if err := xml.Unmarshal(TestXml, &x); err != nil { - t.Fatalf("Error in unmarshal: %s", err) - } -} - -func TestUnmarshalSavesName(t *testing.T) { - var x Xml - if err := xml.Unmarshal(TestXml, &x); err != nil { - t.Fatalf("Error in unmarshal: %s", err) - } - if len(x.Nodes) == 0 { - t.Fatalf("no nodes present") - } - for _, node := range x.Nodes { - if node.XMLName.Local == "person" { - if node.Name == nil { - t.Fatalf("config > person.name is nil") - } - if *node.Name != "jane" { - t.Fatalf("config > person.name = %q", *node.Name) - } - return - } - } - - t.Fatalf("could not find config > person") -} - -func TestUnmarshalSavesUuid(t *testing.T) { - var x Xml - if err := xml.Unmarshal(TestXml, &x); err != nil { - t.Fatalf("Error in unmarshal: %s", err) - } - if len(x.Nodes) == 0 { - t.Fatalf("no nodes present") - } - for _, node := range x.Nodes { - if node.XMLName.Local == "unique" { - if node.Uuid == nil { - t.Fatalf("config > unique.uuid is nil") - } - if *node.Uuid != "1234-56-789" { - t.Fatalf("config > unique.uuid = %q", *node.Uuid) - } - return - } - } - - t.Fatalf("could not find config > unique") -} - -func TestUnmarshalSavesText(t *testing.T) { - var x Xml - if err := xml.Unmarshal(TestXml, &x); err != nil { - t.Fatalf("Error in unmarshal: %s", err) - } - if len(x.Nodes) == 0 { - t.Fatalf("no nodes present") - } - for _, node := range x.Nodes { - if node.XMLName.Local == "normal" { - if len(node.Text) == 0 { - t.Fatalf("config > normal.text is empty") - } - if string(node.Text) != " some text " { - t.Fatalf("config > normal.text = %q", *node.Uuid) - } - return - } - } - - t.Fatalf("could not find config > normal") -} - -func TestMarshalDoesNotContainThings(t *testing.T) { - var x Xml - if err := xml.Unmarshal(TestXml, &x); err != nil { - t.Fatalf("Error in unmarshal: %s", err) - } - - b, err := xml.Marshal(x) - if err != nil { - t.Fatalf("Error in marshal: %s", err) - } - - ans := string(b) - - missing := []string{"10.2.0", "foobar", "Node"} - - for _, chk := range missing { - if strings.Contains(ans, chk) { - t.Fatalf("marshalled bytes includes %q: %s", chk, ans) - } - } -}