diff --git a/client.go b/client.go
index 6a200d3..bb61a23 100644
--- a/client.go
+++ b/client.go
@@ -2,13 +2,13 @@ package pango
import (
"bytes"
+ "context"
"crypto/tls"
"encoding/json"
"encoding/xml"
"fmt"
"io"
- "io/ioutil"
- "log"
+ "log/slog"
"mime"
"mime/multipart"
"net/http"
@@ -19,2098 +19,1242 @@ import (
"time"
"github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/generic"
"github.com/PaloAltoNetworks/pango/plugin"
"github.com/PaloAltoNetworks/pango/util"
"github.com/PaloAltoNetworks/pango/version"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
)
-// These bit flags control what is logged by client connections. Of the flags
-// available for use, LogSend and LogReceive will log ALL communication between
-// the connection object and the PAN-OS XML API. The API key being used for
-// communication will be blanked out, but no other sensitive data will be. As
-// such, those two flags should be considered for debugging only. To disable
-// all logging, set the logging level as LogQuiet.
-//
-// As of right now, pango is not officially supported by Palo Alto Networks TAC,
-// however using the API itself via cURL is. If you run into an issue and you believe
-// it to be a PAN-OS problem, you can enable a cURL output logging style to have pango
-// output an equivalent cURL command to use when interfacing with TAC.
-//
-// If you want to get the cURL command so that you can run it yourself, then set
-// the LogCurlWithPersonalData flag, which will output your real API key, hostname,
-// and any custom headers you have configured the client to send to PAN-OS.
-//
-// The bit-wise flags are as follows:
-//
-// * LogQuiet: disables all logging
-// * LogAction: action being performed (Set / Edit / Delete functions)
-// * LogQuery: queries being run (Get / Show functions)
-// * LogOp: operation commands (Op functions)
-// * LogUid: User-Id commands (Uid functions)
-// * LogLog: log retrieval commands
-// * LogExport: log export commands
-// * LogXpath: the resultant xpath
-// * LogSend: xml docuemnt being sent
-// * LogReceive: xml responses being received
-// * LogOsxCurl: output an OSX cURL command for the data being sent in
-// * LogCurlWithPersonalData: If doing a curl style logging, then include
-// personal data in the curl command instead of tokens.
-const (
- LogQuiet = 1 << (iota + 1)
- LogAction
- LogQuery
- LogOp
- LogUid
- LogLog
- LogExport
- LogImport
- LogXpath
- LogSend
- LogReceive
- LogOsxCurl
- LogCurlWithPersonalData
-)
+type LoggingInfo struct {
+ SLogHandler slog.Handler `json:"-"`
+ LogCategories LogCategory `json:"-"`
+ LogSymbols []string `json:"log_symbols"`
+ LogLevel slog.Level `json:"log_level"`
+}
-// Client is a generic connector struct. It provides wrapper functions for
-// invoking the various PAN-OS XPath API methods. After creating the client,
-// invoke Initialize() to prepare it for use.
-//
-// Many of the functions attached to this struct will take a param named
-// `extras`. Under normal circumstances this will just be nil, but if you have
-// some extra values you need to send in with your request you can specify them
-// here.
-//
-// Likewise, a lot of these functions will return a slice of bytes. Under normal
-// circumstances, you don't need to do anything with this, but sometimes you do,
-// so you can find the raw XML returned from PAN-OS there.
+// Client is an XML API client connection. If provides wrapper functions
+// for invoking the various PAN-OS API methods. After creating the client,
+// invoke Setup() followed by Initialize() to prepare it for use.
type Client struct {
- // Connection properties.
- Hostname string `json:"hostname"`
- Username string `json:"username"`
- Password string `json:"password"`
- ApiKey string `json:"api_key"`
- Protocol string `json:"protocol"`
- Port uint `json:"port"`
- Timeout int `json:"timeout"`
- Target string `json:"target"`
- Headers map[string]string `json:"headers"`
+ Hostname string `json:"hostname"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+ ApiKey string `json:"api_key"`
+ Protocol string `json:"protocol"`
+ Port int `json:"port"`
+ Target string `json:"target"`
+ ApiKeyInRequest bool `json:"api_key_in_request"`
+ Headers map[string]string `json:"headers"`
+ Logging LoggingInfo `json:"logging"`
// Set to true if you want to check environment variables
// for auth and connection properties.
- CheckEnvironment bool `json:"-"`
+ CheckEnvironment bool `json:"-"`
+ AuthFile string `json:"-"`
- // HTTP transport options. Note that the VerifyCertificate setting is
- // only used if you do not specify a HTTP transport yourself.
- VerifyCertificate bool `json:"verify_certificate"`
- Transport *http.Transport `json:"-"`
+ // HTTP transport options. Note that the SkipVerifyCertificate setting
+ // is only used if you do not specify a HTTP transport yourself.
+ SkipVerifyCertificate bool `json:"skip_verify_certificate"`
+ Transport *http.Transport `json:"-"`
// Variables determined at runtime.
- Version version.Number `json:"-"`
- SystemInfo map[string]string `json:"-"`
- Plugin []plugin.Info `json:"-"`
- MultiConfigure *MultiConfigure `json:"-"`
-
- // Logging level.
- Logging uint32 `json:"-"`
- LoggingFromInitialize []string `json:"logging"`
+ Version version.Number `json:"-"`
+ SystemInfo map[string]string `json:"-"`
+ Plugin []plugin.Info `json:"-"`
// Internal variables.
- credsFile string
con *http.Client
api_url string
- configTree *util.XmlNode
+ configTree *generic.Xml
+ logger *categoryLogger
// Variables for testing, response bytes, headers, and response index.
- rp []url.Values
- rb [][]byte
- rh []http.Header
- ri int
+ testInput []*http.Request
+ testOutput []*http.Response
+ testIndex int
authFileContent []byte
}
-// String is the string representation of a client connection. Both the
-// password and API key are replaced with stars, if set, making it safe
-// to print the client connection in log messages.
-func (c *Client) String() string {
- var passwd string
- var api_key string
-
- if c.Password == "" {
- passwd = ""
- } else {
- passwd = "********"
- }
-
- if c.ApiKey == "" {
- api_key = ""
- } else {
- api_key = "********"
- }
-
- return fmt.Sprintf(
- "{Hostname:%s Username:%s Password:%s ApiKey:%s Protocol:%s Port:%d Timeout:%d Logging:%d}",
- c.Hostname, c.Username, passwd, api_key, c.Protocol, c.Port, c.Timeout, c.Logging)
-}
-
-// Versioning returns the client version number.
+// Versioning returns the version number of PAN-OS.
func (c *Client) Versioning() version.Number {
return c.Version
}
-// Plugins returns the plugin information.
+// Plugins returns the list of plugins.
func (c *Client) Plugins() []plugin.Info {
return c.Plugin
}
-// Initialize does some initial setup of the Client connection, retrieves
-// the API key if it was not already present, then performs "show system
-// info" to get the PAN-OS version. The full results are saved into the
-// client's SystemInfo map.
-//
-// If not specified, the following is assumed:
-// * Protocol: https
-// * Port: (unspecified)
-// * Timeout: 10
-// * Logging: LogAction | LogUid
-func (c *Client) Initialize() error {
- if len(c.rb) == 0 {
- var e error
-
- if e = c.initCon(); e != nil {
- return e
- } else if e = c.initApiKey(); e != nil {
- return e
- } else if e = c.initSystemInfo(); e != nil {
- return e
- }
- } else {
- c.Hostname = "localhost"
- c.ApiKey = "password"
- }
-
- return nil
-}
-
-// InitializeUsing does Initialize(), but takes in a filename that contains
-// fallback authentication credentials if they aren't specified.
-//
-// The order of preference for auth / connection settings is:
-//
-// * explicitly set
-// * environment variable (set chkenv to true to enable this)
-// * json file
-func (c *Client) InitializeUsing(filename string, chkenv bool) error {
- c.CheckEnvironment = chkenv
- c.credsFile = filename
-
- return c.Initialize()
+// GetTarget returns the Target param, used in certain API calls.
+func (c *Client) GetTarget() string {
+ return c.Target
}
-// RetrieveApiKey retrieves the API key, which will require that both the
-// username and password are defined.
-//
-// The currently set ApiKey is forgotten when invoking this function.
-func (c *Client) RetrieveApiKey() error {
- c.LogAction("%s: Retrieving API key", c.Hostname)
+// IsPanorama returns true if this is Panorama.
+func (c *Client) IsPanorama() (bool, error) {
+ if len(c.SystemInfo) == 0 {
+ return false, fmt.Errorf("SystemInfo is nil")
+ }
- type key_gen_ans struct {
- Key string `xml:"result>key"`
+ model, ok := c.SystemInfo["model"]
+ if !ok {
+ return false, fmt.Errorf("model not present in SystemInfo")
}
- c.ApiKey = ""
- ans := key_gen_ans{}
- data := url.Values{}
- data.Add("user", c.Username)
- data.Add("password", c.Password)
- data.Add("type", "keygen")
+ return model == "Panorama" || strings.HasPrefix(model, "M-"), nil
+}
- _, _, err := c.Communicate(data, &ans)
+// IsFirewall returns true if this PAN-OS seems to be a NGFW instance.
+func (c *Client) IsFirewall() (bool, error) {
+ ans, err := c.IsPanorama()
if err != nil {
- return err
+ return ans, err
}
- c.ApiKey = ans.Key
-
- return nil
+ return !ans, nil
}
-// EntryListUsing retrieves an list of entries using the given function, either
-// Get or Show.
-func (c *Client) EntryListUsing(fn util.Retriever, path []string) ([]string, error) {
+// Setup does validation and initialization in preparation to start executing API
+// commands against PAN-OS.
+func (c *Client) Setup() error {
var err error
- type Entry struct {
- Name string `xml:"name,attr"`
+
+ // Load up the JSON config file.
+ var json_client Client
+ if c.AuthFile != "" {
+ var b []byte
+ if len(c.testOutput) == 0 {
+ b, err = os.ReadFile(c.AuthFile)
+ } else {
+ b = c.authFileContent
+ }
+
+ if err != nil {
+ return err
+ }
+
+ if err = json.Unmarshal(b, &json_client); err != nil {
+ return err
+ }
}
- type resp_struct struct {
- Entries []Entry `xml:"result>entry"`
+ // Hostname.
+ if c.Hostname == "" {
+ if val := os.Getenv("PANOS_HOSTNAME"); c.CheckEnvironment && val != "" {
+ c.Hostname = val
+ } else if json_client.Hostname != "" {
+ c.Hostname = json_client.Hostname
+ }
}
- if path == nil {
- return nil, fmt.Errorf("xpath is empty")
+ // Username.
+ if c.Username == "" {
+ if val := os.Getenv("PANOS_USERNAME"); c.CheckEnvironment && val != "" {
+ c.Username = val
+ } else {
+ c.Username = json_client.Username
+ }
}
- path = append(path, "entry", "@name")
- resp := resp_struct{}
- _, err = fn(path, nil, &resp)
- if err != nil {
- e2, ok := err.(errors.Panos)
- if ok && e2.ObjectNotFound() {
- return nil, nil
+ // Password.
+ if c.Password == "" {
+ if val := os.Getenv("PANOS_PASSWORD"); c.CheckEnvironment && val != "" {
+ c.Password = val
+ } else {
+ c.Password = json_client.Password
}
- return nil, err
}
- ans := make([]string, len(resp.Entries))
- for i := range resp.Entries {
- ans[i] = resp.Entries[i].Name
+ // API key.
+ if c.ApiKey == "" {
+ if val := os.Getenv("PANOS_API_KEY"); c.CheckEnvironment && val != "" {
+ c.ApiKey = val
+ } else {
+ c.ApiKey = json_client.ApiKey
+ }
}
- return ans, nil
-}
+ // Protocol.
+ if c.Protocol == "" {
+ if val := os.Getenv("PANOS_PROTOCOL"); c.CheckEnvironment && val != "" {
+ c.Protocol = val
+ } else if json_client.Protocol != "" {
+ c.Protocol = json_client.Protocol
+ } else {
+ c.Protocol = "https"
+ }
+ }
+ if c.Protocol != "http" && c.Protocol != "https" {
+ return fmt.Errorf("Invalid protocol %q. Must be \"http\" or \"https\"", c.Protocol)
+ }
-// MemberListUsing retrieves an list of members using the given function, either
-// Get or Show.
-func (c *Client) MemberListUsing(fn util.Retriever, path []string) ([]string, error) {
- type resp_struct struct {
- Members []string `xml:"result>member"`
+ // Port.
+ if c.Port == 0 {
+ if val := os.Getenv("PANOS_PORT"); c.CheckEnvironment && val != "" {
+ if cp, err := strconv.Atoi(val); err != nil {
+ return fmt.Errorf("Failed to parse the env port number: %s", err)
+ } else {
+ c.Port = cp
+ }
+ } else if json_client.Port != 0 {
+ c.Port = json_client.Port
+ }
+ }
+ if c.Port > 65535 {
+ return fmt.Errorf("Port %d is out of bounds", c.Port)
}
- if path == nil {
- return nil, fmt.Errorf("xpath is empty")
+ // Target.
+ if c.Target == "" {
+ if val := os.Getenv("PANOS_TARGET"); c.CheckEnvironment && val != "" {
+ c.Target = val
+ } else {
+ c.Target = json_client.Target
+ }
}
- path = append(path, "member")
- resp := resp_struct{}
- _, err := fn(path, nil, &resp)
- if err != nil {
- e2, ok := err.(errors.Panos)
- if ok && e2.ObjectNotFound() {
- return nil, nil
+ // Headers.
+ if len(c.Headers) == 0 {
+ if val := os.Getenv("PANOS_HEADERS"); c.CheckEnvironment && val != "" {
+ if err := json.Unmarshal([]byte(val), &c.Headers); err != nil {
+ return err
+ }
+ }
+ if len(c.Headers) == 0 && len(json_client.Headers) > 0 {
+ c.Headers = make(map[string]string)
+ for k, v := range json_client.Headers {
+ c.Headers[k] = v
+ }
}
- return nil, err
}
- return resp.Members, nil
-}
+ // Skip verify cert.
+ if !c.SkipVerifyCertificate {
+ if val := os.Getenv("PANOS_SKIP_VERIFY_CERTIFICATE"); c.CheckEnvironment && val != "" {
+ if vcb, err := strconv.ParseBool(val); err != nil {
+ return err
+ } else if vcb {
+ c.SkipVerifyCertificate = vcb
+ }
+ }
+ if !c.SkipVerifyCertificate && json_client.SkipVerifyCertificate {
+ c.SkipVerifyCertificate = true
+ }
+ }
-// RequestPasswordHash requests a password hash of the given string.
-func (c *Client) RequestPasswordHash(val string) (string, error) {
- c.LogOp("(op) creating password hash")
- type phash_req struct {
- XMLName xml.Name `xml:"request"`
- Val string `xml:"password-hash>password"`
+ err = c.setupLogging(c.Logging)
+ if err != nil {
+ return err
}
- type phash_ans struct {
- Hash string `xml:"result>phash"`
+ // Setup the client.
+ if c.Transport == nil {
+ c.Transport = &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: c.SkipVerifyCertificate,
+ },
+ }
+ }
+ c.con = &http.Client{
+ Transport: c.Transport,
}
- req := phash_req{Val: val}
- ans := phash_ans{}
+ // Sanity check.
+ if c.Hostname == "" {
+ return fmt.Errorf("hostname must be specified")
+ }
+ if c.ApiKey == "" && (c.Username == "" && c.Password == "") {
+ return fmt.Errorf("either API key or both username and password must be specified")
+ }
- if _, err := c.Op(req, "", nil, &ans); err != nil {
- return "", err
+ // Configure the api url.
+ if c.Port == 0 {
+ c.api_url = fmt.Sprintf("%s://%s/api", c.Protocol, c.Hostname)
+ } else {
+ c.api_url = fmt.Sprintf("%s://%s:%d/api", c.Protocol, c.Hostname, c.Port)
}
- return ans.Hash, nil
+ return nil
}
-// ValidateConfig performs a commit config validation check.
-//
-// Setting sync to true means that this function will block until the job
-// finishes.
-//
-//
-// The sleep param is an optional sleep duration to wait between polling for
-// job completion. This param is only used if sync is set to true.
+// SetupLocalInspection configures the client for local inspection.
//
-// This function returns the job ID and if any errors were encountered.
-func (c *Client) ValidateConfig(sync bool, sleep time.Duration) (uint, error) {
+// The version number is taken from the schema if it's present. If it is not
+// present, then the version number falls back to what's specified in panosVersion.
+func (c *Client) SetupLocalInspection(schema, panosVersion string) error {
var err error
- c.LogOp("(op) validating config")
- type op_req struct {
- XMLName xml.Name `xml:"validate"`
- Cmd string `xml:"full"`
- }
- job_ans := util.JobResponse{}
- _, err = c.Op(op_req{}, "", nil, &job_ans)
- if err != nil {
- return 0, err
+ if err = c.LoadPanosConfig([]byte(schema)); err != nil {
+ return err
}
- id := job_ans.Id
- if !sync {
- return id, nil
+ if c.Version.Major == 0 && c.Version.Minor == 0 && c.Version.Patch == 0 {
+ if panosVersion == "" {
+ return fmt.Errorf("no version found in the schema; the version must be specified")
+ }
+
+ c.Version, err = version.New(panosVersion)
+ if err != nil {
+ return err
+ }
}
- return id, c.WaitForJob(id, sleep, nil, nil)
+ return nil
}
-// RevertToRunningConfig discards any changes made and reverts to the last
-// config committed.
-func (c *Client) RevertToRunningConfig() error {
- c.LogOp("(op) reverting to running config")
- _, err := c.Op("running-config.xml", "", nil, nil)
- return err
+// Initialize retrieves the API key if needed then retrieves the system info.
+func (c *Client) Initialize(ctx context.Context) error {
+ var err error
+
+ if c.ApiKey == "" {
+ if err = c.RetrieveApiKey(ctx); err != nil {
+ return err
+ }
+ }
+
+ if err = c.RetrieveSystemInfo(ctx); err != nil {
+ return err
+ }
+
+ if err = c.RetrievePlugins(ctx); err != nil {
+ return err
+ }
+
+ return nil
}
-// ConfigLocks returns any config locks that are currently in place.
-//
-// If vsys is an empty string, then the vsys will default to "shared".
-func (c *Client) ConfigLocks(vsys string) ([]util.Lock, error) {
+// RetrieveSystemInfo performs "show system info" and saves it SystemInfo.
+func (c *Client) RetrieveSystemInfo(ctx context.Context) error {
var err error
- var cmd string
- ans := configLocks{}
- if vsys == "" {
- vsys = "shared"
+ type req struct {
+ XMLName xml.Name `xml:"show"`
+ Cmd string `xml:"system>info"`
}
- if c.Version.Gte(version.Number{9, 1, 0, ""}) {
- var tgt string
- if vsys == "shared" {
- tgt = "all"
- } else {
- tgt = vsys
- }
- cmd = fmt.Sprintf("%s", tgt)
- } else {
- cmd = ""
+ type system struct {
+ Tags []generic.Xml `xml:",any"`
}
- c.LogOp("(op) getting config locks for scope %q", vsys)
- _, err = c.Op(cmd, vsys, nil, &ans)
- if err != nil {
- return nil, err
+ type resp struct {
+ System system `xml:"result>system"`
}
- return ans.Locks, nil
-}
-// LockConfig locks the config for the given scope with the given comment.
-//
-// If vsys is an empty string, the scope defaults to "shared".
-func (c *Client) LockConfig(vsys, comment string) error {
- if vsys == "" {
- vsys = "shared"
+ cmd := &xmlapi.Op{
+ Command: req{},
+ Target: c.Target,
}
- c.LogOp("(op) locking config for scope %q", vsys)
- var inner string
- if comment == "" {
- inner = ""
- } else {
- inner = fmt.Sprintf("%s", comment)
+ var ans resp
+ if _, _, err = c.Communicate(ctx, cmd, false, &ans); err != nil {
+ return err
}
- cmd := fmt.Sprintf("%s", inner)
- _, err := c.Op(cmd, vsys, nil, nil)
- return err
+ c.SystemInfo = make(map[string]string, len(ans.System.Tags))
+ for _, t := range ans.System.Tags {
+ if t.TrimmedText == nil {
+ continue
+ }
+ c.SystemInfo[t.XMLName.Local] = *t.TrimmedText
+ if t.XMLName.Local == "sw-version" {
+ c.Version, err = version.New(*t.TrimmedText)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
}
-// UnlockConfig removes the config lock on the given scope.
+// RetrievePanosConfig retrieves the running config, candidate config,
+// or the specified saved config file.
//
-// If vsys is an empty string, the scope defaults to "shared".
-func (c *Client) UnlockConfig(vsys string) error {
- if vsys == "" {
- vsys = "shared"
+// If the name is candidate, then the candidate config is retrieved. If the
+// name is running, then the running config is retrieved. Otherwise, then
+// the name is assumed to be the name of the saved config.
+func (c *Client) RetrievePanosConfig(ctx context.Context, name string) ([]byte, error) {
+ type req struct {
+ XMLName xml.Name `xml:"show"`
+ Running *string `xml:"config>running"`
+ Candidate *string `xml:"config>candidate"`
+ Saved *string `xml:"config>saved"`
}
- type cmd struct {
- XMLName xml.Name `xml:"request"`
- Cmd string `xml:"config-lock>remove"`
+ type rdata struct {
+ Data []byte `xml:",innerxml"`
}
- c.LogOp("(op) unlocking config for scope %q", vsys)
- _, err := c.Op(cmd{}, vsys, nil, nil)
- return err
-}
+ type resp struct {
+ XMLName xml.Name `xml:"response"`
+ Result rdata `xml:"result"`
+ }
-// CommitLocks returns any commit locks that are currently in place.
-//
-// If vsys is an empty string, then the vsys will default to "shared".
-func (c *Client) CommitLocks(vsys string) ([]util.Lock, error) {
- if vsys == "" {
- vsys = "shared"
+ var s string
+ cs := req{}
+ switch name {
+ case "candidate":
+ cs.Candidate = &s
+ case "running":
+ cs.Running = &s
+ default:
+ cs.Saved = &name
}
- c.LogOp("(op) getting commit locks for scope %q", vsys)
- ans := commitLocks{}
- _, err := c.Op("", vsys, nil, &ans)
- if err != nil {
+ cmd := &xmlapi.Op{
+ Command: cs,
+ Target: c.Target,
+ }
+
+ var ans resp
+ if _, _, err := c.Communicate(ctx, cmd, false, &ans); err != nil {
return nil, err
}
- return ans.Locks, nil
+
+ return ans.Result.Data, nil
}
-// LockCommits locks commits for the given scope with the given comment.
+// RetrieveApiKey refreshes the API key.
//
-// If vsys is an empty string, the scope defaults to "shared".
-func (c *Client) LockCommits(vsys, comment string) error {
- if vsys == "" {
- vsys = "shared"
+// This function unsets the ApiKey value and thus requires that the Username and Password
+// be specified.
+func (c *Client) RetrieveApiKey(ctx context.Context) error {
+ type key_gen_ans struct {
+ Key string `xml:"result>key"`
}
- c.LogOp("(op) locking commits for scope %q", vsys)
- var inner string
- if comment == "" {
- inner = ""
- } else {
- inner = fmt.Sprintf("%s", comment)
+ cmd := &xmlapi.KeyGen{
+ Username: c.Username,
+ Password: c.Password,
}
- cmd := fmt.Sprintf("%s", inner)
- _, err := c.Op(cmd, vsys, nil, nil)
- return err
+ var ans key_gen_ans
+ _, _, err := c.Communicate(ctx, cmd, false, &ans)
+ if err != nil {
+ return err
+ }
+
+ c.ApiKey = ans.Key
+
+ return nil
}
-// UnlockCommits removes the commit lock on the given scope owned by the given
-// admin, if this admin is someone other than the current acting admin.
-//
-// If vsys is an empty string, the scope defaults to "shared".
-func (c *Client) UnlockCommits(vsys, admin string) error {
- if vsys == "" {
- vsys = "shared"
+// RetrievePlugins refreshes the Plugins info from PAN-OS.
+func (c *Client) RetrievePlugins(ctx context.Context) error {
+ cmd := &xmlapi.Op{
+ Command: plugin.GetPlugins{},
}
- type cmd struct {
- XMLName xml.Name `xml:"request"`
- Admin string `xml:"commit-lock>remove>admin,omitempty"`
+ var ans plugin.PackageListing
+ _, _, err := c.Communicate(ctx, cmd, false, &ans)
+ if err != nil {
+ return err
}
- c.LogOp("(op) unlocking commits for scope %q", vsys)
- _, err := c.Op(cmd{Admin: admin}, vsys, nil, nil)
- return err
+ c.Plugin = ans.Listing()
+
+ return nil
}
-// WaitForJob polls the device, waiting for the specified job to finish.
+// LoadPanosConfig stores the given XML document into this client, allowing
+// the user to use various namespace functions to query the config. This
+// is referred to as local inspection mode.
//
-// The sleep param is the length of time to wait between polling for job
-// completion.
+// The config given must be in the form of `...`.
//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// If you want to unmarshal the response into a struct, then pass in a
-// pointer to the struct for the "resp" param. If you just want to know if
-// the job completed with a status other than "FAIL", you only need to check
-// the returned error message.
-//
-// In the case that there are multiple errors returned from the job, the first
-// error is returned as the error string, and no unmarshaling is attempted.
-func (c *Client) WaitForJob(id uint, sleep time.Duration, extras, resp interface{}) error {
- var err error
- var prev uint
- var data []byte
- dp := false
- all_ok := true
-
- c.LogOp("(op) waiting for job %d", id)
- type op_req struct {
- XMLName xml.Name `xml:"show"`
- Id uint `xml:"jobs>id"`
+// If the 'detail-version' attribute is present in the XML, then it's saved to this
+// instance's Version attribute.
+func (c *Client) LoadPanosConfig(config []byte) error {
+ var ans generic.Xml
+ if err := xml.Unmarshal(config, &ans); err != nil {
+ return err
}
- req := op_req{Id: id}
- var ans util.BasicJob
- for {
- // We need to zero out the response each iteration because the slices
- // of strings append to each other instead of zeroing out.
- ans = util.BasicJob{}
+ if ans.XMLName.Local != "config" {
+ return fmt.Errorf("Expected \"config\" at the root, found %q", ans.XMLName.Local)
+ }
- // Get current percent complete.
- data, err = c.Op(req, "", extras, &ans)
+ if ans.DetailedVersion != nil {
+ vn, err := version.New(*ans.DetailedVersion)
if err != nil {
return err
}
-
- // Output percent complete if it's new.
- if ans.Progress != prev {
- prev = ans.Progress
- c.LogOp("(op) job %d: %d percent complete", id, prev)
- }
-
- // Check for device commits.
- all_done := true
- for _, d := range ans.Devices {
- c.LogOp("%q result: %s", d.Serial, d.Result)
- if d.Result == "PEND" {
- all_done = false
- break
- } else if d.Result != "OK" && all_ok {
- all_ok = false
- }
- }
-
- // Check for end condition.
- if ans.Progress == 100 {
- if all_done {
- break
- } else if !dp {
- c.LogOp("(op) Waiting for %d device commits ...", len(ans.Devices))
- dp = true
- }
- }
-
- if sleep > 0 {
- time.Sleep(sleep)
- }
- }
-
- // Check the results for a failed commit.
- if ans.Result == "FAIL" {
- if len(ans.Details.Lines) > 0 {
- return fmt.Errorf(ans.Details.String())
- } else {
- return fmt.Errorf("Job %d has failed to complete successfully", id)
- }
- } else if !all_ok {
- return fmt.Errorf("Commit failed on one or more devices")
+ c.Version = vn
}
- if resp == nil {
- return nil
+ c.configTree = &generic.Xml{
+ XMLName: xml.Name{
+ Local: "a",
+ },
+ Nodes: []generic.Xml{ans},
}
- return xml.Unmarshal(data, resp)
+ return nil
}
-// LogAction writes a log message for SET/EDIT/DELETE operations if LogAction is set.
-func (c *Client) LogAction(msg string, i ...interface{}) {
- if c.Logging&LogAction == LogAction {
- log.Printf(msg, i...)
+// ReadFromConfig returns the XML at the given XPATH location. This is
+// referred to as local inspection mode.
+//
+// If the XPATH is a listing, then set withPackaging to false.
+func (c *Client) ReadFromConfig(ctx context.Context, path []string, withPackaging bool, ans any) ([]byte, error) {
+ if c.configTree == nil {
+ return nil, fmt.Errorf("no config loaded")
}
-}
-// LogQuery writes a log message for GET/SHOW operations if LogQuery is set.
-func (c *Client) LogQuery(msg string, i ...interface{}) {
- if c.Logging&LogQuery == LogQuery {
- log.Printf(msg, i...)
+ if len(path) == 0 {
+ return nil, fmt.Errorf("path is empty")
}
-}
-// LogOp writes a log message for OP operations if LogOp is set.
-func (c *Client) LogOp(msg string, i ...interface{}) {
- if c.Logging&LogOp == LogOp {
- log.Printf(msg, i...)
- }
-}
+ entryPrefix := "entry[@name='"
+ entrySuffix := "']"
-// LogUid writes a log message for User-Id operations if LogUid is set.
-func (c *Client) LogUid(msg string, i ...interface{}) {
- if c.Logging&LogUid == LogUid {
- log.Printf(msg, i...)
- }
-}
-
-// LogLog writes a log message for LOG operations if LogLog is set.
-func (c *Client) LogLog(msg string, i ...interface{}) {
- if c.Logging&LogLog == LogLog {
- log.Printf(msg, i...)
- }
-}
+ config := c.configTree
+ for _, pp := range path {
+ var tag, name string
+ if strings.HasPrefix(pp, entryPrefix) && strings.HasSuffix(pp, entrySuffix) {
+ tag = "entry"
+ name = strings.TrimSuffix(strings.TrimPrefix(pp, entryPrefix), entrySuffix)
+ } else {
+ tag = pp
+ }
-// LogExport writes a log message for EXPORT operations if LogExport is set.
-func (c *Client) LogExport(msg string, i ...interface{}) {
- if c.Logging&LogExport == LogExport {
- log.Printf(msg, i...)
- }
-}
+ found := false
+ for _, node := range config.Nodes {
+ if node.XMLName.Local == tag && (node.Name == nil || *node.Name == name) {
+ found = true
+ config = &node
+ break
+ }
+ }
-// LogImport writes a log message for IMPORT operations if LogImport is set.
-func (c *Client) LogImport(msg string, i ...interface{}) {
- if c.Logging&LogImport == LogImport {
- log.Printf(msg, i...)
+ if !found {
+ config = nil
+ break
+ }
}
-}
-// Communicate sends the given data to PAN-OS.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-//
-// Even if an answer struct is given, we first check for known error formats. If
-// a known error format is detected, unmarshalling into the answer struct is not
-// performed.
-//
-// If the API key is set, but not present in the given data, then it is added in.
-func (c *Client) Communicate(data url.Values, ans interface{}) ([]byte, http.Header, error) {
- if c.ApiKey != "" && data.Get("key") == "" {
- data.Set("key", c.ApiKey)
+ if config == nil {
+ return nil, errors.ObjectNotFound()
}
- c.logSend(data)
-
- body, hdrs, err := c.post(data)
+ b, err := xml.Marshal(config)
if err != nil {
- return body, hdrs, err
+ return b, err
}
- return body, hdrs, c.endCommunication(body, ans)
-}
-
-// CommunicateFile does a file upload to PAN-OS.
-//
-// The content param is the content of the file you want to upload.
-//
-// The filename param is the basename of the file you want to specify in the
-// multipart form upload.
-//
-// The fp param is the name of the param for the file upload.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-//
-// Even if an answer struct is given, we first check for known error formats. If
-// a known error format is detected, unmarshalling into the answer struct is not
-// performed.
-//
-// If the API key is set, but not present in the given data, then it is added in.
-func (c *Client) CommunicateFile(content, filename, fp string, data url.Values, ans interface{}) ([]byte, http.Header, error) {
- var err error
-
- if c.ApiKey != "" && data.Get("key") == "" {
- data.Set("key", c.ApiKey)
+ var newb []byte
+ if !withPackaging {
+ newb = append([]byte(nil), b...)
+ } else {
+ newb = make([]byte, 0, len(b)+7)
+ newb = append(newb, []byte("")...)
+ newb = append(newb, b...)
+ newb = append(newb, []byte("
")...)
}
- c.logSend(data)
-
- buf := bytes.Buffer{}
- w := multipart.NewWriter(&buf)
-
- for k := range data {
- w.WriteField(k, data.Get(k))
+ if ans == nil {
+ return newb, nil
}
- w2, err := w.CreateFormFile(fp, filename)
- if err != nil {
- return nil, nil, err
- }
+ err = xml.Unmarshal(newb, ans)
+ return newb, err
+}
- if _, err = io.Copy(w2, strings.NewReader(content)); err != nil {
- return nil, nil, err
+func (c *Client) Clock(ctx context.Context) (time.Time, error) {
+ type creq struct {
+ XMLName xml.Name `xml:"show"`
+ Cmd string `xml:"clock"`
}
- w.Close()
-
- req, err := http.NewRequest("POST", c.api_url, &buf)
- if err != nil {
- return nil, nil, err
- }
- req.Header.Set("Content-Type", w.FormDataContentType())
- for k, v := range c.Headers {
- req.Header.Set(k, v)
+ type cresp struct {
+ Result string `xml:"result"`
}
- res, err := c.con.Do(req)
- if err != nil {
- return nil, nil, err
+ cmd := &xmlapi.Op{
+ Command: creq{},
+ Target: c.Target,
}
+ var ans cresp
- defer res.Body.Close()
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- return body, res.Header, err
+ if _, _, err := c.Communicate(ctx, cmd, false, &ans); err != nil {
+ return time.Time{}, err
}
- return body, res.Header, c.endCommunication(body, ans)
+ return time.Parse(time.UnixDate+"\n", ans.Result)
}
-// Op runs an operational or "op" type command.
-//
-// The req param can be either a properly formatted XML string or a struct
-// that can be marshalled into XML.
-//
-// The vsys param is the vsys the op command should be executed in, if any.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
+// MultiConfig does a "multi-config" type command.
//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
+// Param strict should be true if you want strict transactional support.
//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Op(req interface{}, vsys string, extras, ans interface{}) ([]byte, error) {
- var err error
- data := url.Values{}
- data.Set("type", "op")
-
- if err = addToData("cmd", req, true, &data); err != nil {
- return nil, err
+// Note that the error returned from this function is only if there was an error
+// unmarshaling the response into the the multi config response struct. If the
+// multi config itself failed, then the reason can be found in its results.
+func (c *Client) MultiConfig(ctx context.Context, mc *xmlapi.MultiConfig, strict bool, extras url.Values) ([]byte, *http.Response, *xmlapi.MultiConfigResponse, error) {
+ cmd := &xmlapi.Config{
+ Action: "multi-config",
+ Element: mc,
+ StrictTransactional: strict,
+ Target: c.Target,
}
- if vsys != "" {
- data.Set("vsys", vsys)
+ text, httpResp, err := c.Communicate(ctx, cmd, false, nil)
+
+ // If the text is empty, then the err will have a real error we should report to the
+ // invoker. However, if the text is not empty, then ignore the error and parse the
+ // multi config results using the specialized struct custom designed to handle it.
+ if len(text) == 0 {
+ return text, httpResp, nil, err
}
- if c.Target != "" {
- data.Set("target", c.Target)
+ mcResp, err := xmlapi.NewMultiConfigResponse(text)
+ if err != nil {
+ return text, httpResp, nil, err
}
- if err = mergeUrlValues(&data, extras); err != nil {
- return nil, err
+ if !mcResp.Ok() {
+ return text, httpResp, mcResp, mcResp
}
- b, _, err := c.Communicate(data, ans)
- return b, err
+ return text, httpResp, mcResp, nil
}
-// Log submits a "log" command.
-//
-// Use `WaitForLogs` to get the results of the log command.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Log(logType, action, query, dir string, nlogs, skip int, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- data.Set("type", "log")
-
- if logType != "" {
- data.Set("log-type", logType)
+// RequestPasswordHash requests a password hash of the given string.
+func (c *Client) RequestPasswordHash(ctx context.Context, v string) (string, error) {
+ type phash_req struct {
+ XMLName xml.Name `xml:"request"`
+ Val string `xml:"password-hash>password"`
}
- if action != "" {
- data.Set("action", action)
+ cmd := &xmlapi.Op{
+ Command: phash_req{
+ Val: v,
+ },
+ Target: c.Target,
}
- if query != "" {
- data.Set("query", query)
+ type phash_ans struct {
+ Hash string `xml:"result>phash"`
}
- if dir != "" {
- data.Set("dir", dir)
+ var ans phash_ans
+ if _, _, err := c.Communicate(ctx, cmd, false, &ans); err != nil {
+ return "", err
}
- if nlogs != 0 {
- data.Set("nlogs", strconv.Itoa(nlogs))
- }
+ return ans.Hash, nil
+}
- if skip != 0 {
- data.Set("skip", strconv.Itoa(skip))
+// ValidateConfig performs a commit config validation check.
+//
+// Use WaitForJob and the uint returned from this function to get the
+// results of the job.
+func (c *Client) ValidateConfig(ctx context.Context, sleep time.Duration) (uint, error) {
+ type req struct {
+ XMLName xml.Name `xml:"validate"`
+ Cmd string `xml:"full"`
}
- if err := mergeUrlValues(&data, extras); err != nil {
- return nil, err
+ cmd := &xmlapi.Op{
+ Command: req{},
+ Target: c.Target,
}
- b, _, err := c.Communicate(data, ans)
- return b, err
+ id, _, _, err := c.StartJob(ctx, cmd)
+ return id, err
}
-// WaitForLogs performs repeated log retrieval operations until the log job is complete
-// or the timeout is reached.
+// WaitForLogs polls PAN-OS until the given log retrieval job is complete.
//
-// Specify a timeout of zero to wait indefinitely.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) WaitForLogs(id uint, sleep, timeout time.Duration, ans interface{}) ([]byte, error) {
+// The sleep param is the time to wait between polling. Note that a sleep
+// time less than 2 seconds may cause PAN-OS to take longer to finish the
+// job.
+func (c *Client) WaitForLogs(ctx context.Context, id uint, sleep time.Duration, resp any) ([]byte, error) {
+ if id == 0 {
+ return nil, fmt.Errorf("job ID must be specified")
+ }
+
var err error
var data []byte
var prev string
- start := time.Now()
- end := start.Add(timeout)
- extras := url.Values{}
- extras.Set("job-id", fmt.Sprintf("%d", id))
- c.LogLog("(log) waiting for logs: %d", id)
+ cmd := &xmlapi.Log{
+ Action: "get",
+ JobId: id,
+ }
- var resp util.BasicJob
+ var ans util.BasicJob
for {
- resp = util.BasicJob{}
+ ans = util.BasicJob{}
- data, err = c.Log("", "get", "", "", 0, 0, extras, &resp)
+ data, _, err = c.Communicate(ctx, cmd, false, &ans)
if err != nil {
return data, err
}
- if resp.Status != prev {
- prev = resp.Status
- c.LogLog("(log) job %d status: %s", id, prev)
+ if ans.Status != prev {
+ prev = ans.Status
+ // log %d id and %s prev
}
- if resp.Status == "FIN" {
+ if ans.Status == "FIN" {
break
}
- if timeout > 0 && end.After(time.Now()) {
- return data, fmt.Errorf("timeout")
- }
-
if sleep > 0 {
time.Sleep(sleep)
}
}
- if resp.Result == "FAIL" {
- if len(resp.Details.Lines) > 0 {
- return data, fmt.Errorf(resp.Details.String())
+ if ans.Result == "FAIL" {
+ if len(ans.Details.Lines) > 0 {
+ return data, fmt.Errorf(ans.Details.String())
} else {
- return data, fmt.Errorf("Job %d has failed to complete successfully", id)
+ return data, fmt.Errorf("Job %d has failed", id)
}
}
- if ans == nil {
+ if resp == nil {
return data, nil
}
- err = xml.Unmarshal(data, ans)
+ err = xml.Unmarshal(data, resp)
return data, err
}
-// Show runs a "show" type command.
-//
-// The path param should be either a string or a slice of strings.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Show(path, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- return c.typeConfig("show", data, nil, extras, ans)
-}
-
-// Get runs a "get" type command.
-//
-// The path param should be either a string or a slice of strings.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Get(path, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- return c.typeConfig("get", data, nil, extras, ans)
-}
-
-// Delete runs a "delete" type command, removing the supplied xpath and
-// everything underneath it.
-//
-// The path param should be either a string or a slice of strings.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Delete(path, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- return c.typeConfig("delete", data, nil, extras, ans)
-}
-
-// Set runs a "set" type command, creating the element at the given xpath.
-//
-// The path param should be either a string or a slice of strings.
-//
-// The element param can be either a string of properly formatted XML to send
-// or a struct which can be marshaled into a string.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Set(path, element, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- return c.typeConfig("set", data, element, extras, ans)
-}
-
-// Edit runs a "edit" type command, modifying what is at the given xpath
-// with the supplied element.
-//
-// The path param should be either a string or a slice of strings.
-//
-// The element param can be either a string of properly formatted XML to send
-// or a struct which can be marshaled into a string.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Edit(path, element, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- return c.typeConfig("edit", data, element, extras, ans)
-}
-
-// Move does a "move" type command.
-func (c *Client) Move(path interface{}, where, dst string, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
-
- if where != "" {
- data.Set("where", where)
- }
-
- if dst != "" {
- data.Set("dst", dst)
- }
-
- return c.typeConfig("move", data, nil, extras, ans)
-}
-
-// Rename does a "rename" type command.
-func (c *Client) Rename(path interface{}, newname string, extras, ans interface{}) ([]byte, error) {
- data := url.Values{}
- xp := util.AsXpath(path)
- c.logXpath(xp)
- data.Set("xpath", xp)
- data.Set("newname", newname)
-
- return c.typeConfig("rename", data, nil, extras, ans)
-}
-
-// MultiConfig does a "multi-config" type command.
+// WaitForJob polls PAN-OS until the given job finishes.
//
-// Param strict should be true if you want strict transactional support.
-//
-// Note that the error returned from this function is only if there was an error
-// unmarshaling the response into the the multi config response struct. If the
-// multi config itself failed, then the reason can be found in its results.
-func (c *Client) MultiConfig(element MultiConfigure, strict bool, extras interface{}) ([]byte, MultiConfigureResponse, error) {
- data := url.Values{}
- if strict {
- data.Set("strict-transactional", "yes")
- }
-
- text, _ := c.typeConfig("multi-config", data, element, extras, nil)
-
- resp := MultiConfigureResponse{}
- err := xml.Unmarshal(text, &resp)
- return text, resp, err
-}
-
-// Uid performs User-ID API calls.
-func (c *Client) Uid(cmd interface{}, vsys string, extras, ans interface{}) ([]byte, error) {
+// The sleep param is the time to wait between polling. Note that a sleep
+// time less than 2 seconds may cause PAN-OS to take longer to finish the
+// job.
+func (c *Client) WaitForJob(ctx context.Context, id uint, sleep time.Duration, resp any) error {
var err error
- data := url.Values{}
- data.Set("type", "user-id")
-
- if err = addToData("cmd", cmd, true, &data); err != nil {
- return nil, err
- }
-
- if vsys != "" {
- data.Set("vsys", vsys)
- }
-
- if c.Target != "" {
- data.Set("target", c.Target)
- }
-
- if err = mergeUrlValues(&data, extras); err != nil {
- return nil, err
- }
-
- b, _, err := c.Communicate(data, ans)
- return b, err
-}
-
-// Import performs an import type command.
-//
-// The cat param is the category.
-//
-// The content param is the content of the file you want to upload.
-//
-// The filename param is the basename of the file you want to specify in the
-// multipart form upload.
-//
-// The fp param is the name of the param for the file upload.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-func (c *Client) Import(cat, content, filename, fp string, timeout time.Duration, extras, ans interface{}) ([]byte, error) {
- if timeout < 0 {
- return nil, fmt.Errorf("timeout cannot be negative")
- } else if timeout > 0 {
- defer func(c *Client, v time.Duration) {
- c.con.Timeout = v
- }(c, c.con.Timeout)
- c.con.Timeout = timeout
- }
-
- data := url.Values{}
- data.Set("type", "import")
- data.Set("category", cat)
-
- if err := mergeUrlValues(&data, extras); err != nil {
- return nil, err
- }
-
- b, _, err := c.CommunicateFile(content, filename, fp, data, ans)
- return b, err
-}
-
-// Commit performs PAN-OS commits.
-//
-// The cmd param can be a properly formatted XML string, a struct that can
-// be marshalled into XML, or one of the commit types that can be found in the
-// commit package.
-//
-// The action param is the commit action to be taken. If you are using one of the
-// commit structs as the `cmd` param and the action param is an empty string, then
-// the action is taken from the commit struct passed in.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// Commits result in a job being submitted to the backend. The job ID, assuming
-// the commit action was successfully submitted, the response from the server,
-// and if an error was encountered or not are all returned from this function.
-func (c *Client) Commit(cmd interface{}, action string, extras interface{}) (uint, []byte, error) {
- var err error
- data := url.Values{}
- data.Set("type", "commit")
-
- if err = addToData("cmd", cmd, true, &data); err != nil {
- return 0, nil, err
- }
-
- if action != "" {
- data.Set("action", action)
- } else if ca, ok := cmd.(util.Actioner); ok && ca.Action() != "" {
- data.Set("action", ca.Action())
- }
-
- if c.Target != "" {
- data.Set("target", c.Target)
- }
-
- if err = mergeUrlValues(&data, extras); err != nil {
- return 0, nil, err
- }
-
- ans := util.JobResponse{}
- b, _, err := c.Communicate(data, &ans)
- return ans.Id, b, err
-}
-
-// Export runs an "export" type command.
-//
-// The category param specifies the desired file type to export.
-//
-// The extras param should be either nil or a url.Values{} to be mixed in with
-// the constructed request.
-//
-// The ans param should be a pointer to a struct to unmarshal the response
-// into or nil.
-//
-// Any response received from the server is returned, along with any errors
-// encountered.
-//
-// If the export invoked results in a file being downloaded from PAN-OS, then
-// the string returned is the name of the remote file that is retrieved,
-// otherwise it's just an empty string.
-func (c *Client) Export(category string, timeout time.Duration, extras, ans interface{}) (string, []byte, error) {
- if timeout < 0 {
- return "", nil, fmt.Errorf("timeout cannot be negative")
- } else if timeout > 0 {
- defer func(c *Client, v time.Duration) {
- c.con.Timeout = v
- }(c, c.con.Timeout)
- c.con.Timeout = timeout
- }
-
- data := url.Values{}
- data.Set("type", "export")
-
- if category != "" {
- data.Set("category", category)
- }
+ var prev uint
+ var data []byte
+ dp := false
+ all_ok := true
- if err := mergeUrlValues(&data, extras); err != nil {
- return "", nil, err
+ type req struct {
+ XMLName xml.Name `xml:"show"`
+ Id uint `xml:"jobs>id"`
}
- var filename string
- b, hdrs, err := c.Communicate(data, ans)
- if err == nil && hdrs != nil {
- // Check and see if there's a filename in the content disposition.
- mediatype, params, err := mime.ParseMediaType(hdrs.Get("Content-Disposition"))
- if err == nil && mediatype == "attachment" {
- filename = params["filename"]
- }
+ cmd := &xmlapi.Op{
+ Command: req{
+ Id: id,
+ },
+ Target: c.Target,
}
- return filename, b, err
-}
-
-/*** Internal functions ***/
-
-func (c *Client) initCon() error {
- var tout time.Duration
-
- // Load up the JSON config file.
- json_client := &Client{}
- if c.credsFile != "" {
- var (
- b []byte
- err error
- )
- if len(c.rb) == 0 {
- b, err = ioutil.ReadFile(c.credsFile)
- } else {
- b, err = c.authFileContent, nil
- }
+ var ans util.BasicJob
+ for {
+ ans = util.BasicJob{}
+ data, _, err = c.Communicate(ctx, cmd, false, &ans)
if err != nil {
return err
}
- if err = json.Unmarshal(b, &json_client); err != nil {
- return err
- }
- }
-
- // Hostname.
- if c.Hostname == "" {
- if val := os.Getenv("PANOS_HOSTNAME"); c.CheckEnvironment && val != "" {
- c.Hostname = val
- } else {
- c.Hostname = json_client.Hostname
- }
- }
-
- // Username.
- if c.Username == "" {
- if val := os.Getenv("PANOS_USERNAME"); c.CheckEnvironment && val != "" {
- c.Username = val
- } else {
- c.Username = json_client.Username
- }
- }
-
- // Password.
- if c.Password == "" {
- if val := os.Getenv("PANOS_PASSWORD"); c.CheckEnvironment && val != "" {
- c.Password = val
- } else {
- c.Password = json_client.Password
- }
- }
-
- // API key.
- if c.ApiKey == "" {
- if val := os.Getenv("PANOS_API_KEY"); c.CheckEnvironment && val != "" {
- c.ApiKey = val
- } else {
- c.ApiKey = json_client.ApiKey
- }
- }
-
- // Protocol.
- if c.Protocol == "" {
- if val := os.Getenv("PANOS_PROTOCOL"); c.CheckEnvironment && val != "" {
- c.Protocol = val
- } else if json_client.Protocol != "" {
- c.Protocol = json_client.Protocol
- } else {
- c.Protocol = "https"
- }
- }
- if c.Protocol != "http" && c.Protocol != "https" {
- return fmt.Errorf("Invalid protocol %q. Must be \"http\" or \"https\"", c.Protocol)
- }
-
- // Port.
- if c.Port == 0 {
- if val := os.Getenv("PANOS_PORT"); c.CheckEnvironment && val != "" {
- if cp, err := strconv.Atoi(val); err != nil {
- return fmt.Errorf("Failed to parse the env port number: %s", err)
- } else {
- c.Port = uint(cp)
- }
- } else if json_client.Port != 0 {
- c.Port = json_client.Port
+ if ans.Progress != prev {
+ prev = ans.Progress
+ // log the change.
}
- }
- if c.Port > 65535 {
- return fmt.Errorf("Port %d is out of bounds", c.Port)
- }
- // Timeout.
- if c.Timeout == 0 {
- if val := os.Getenv("PANOS_TIMEOUT"); c.CheckEnvironment && val != "" {
- if ival, err := strconv.Atoi(val); err != nil {
- return fmt.Errorf("Failed to parse timeout env var as int: %s", err)
- } else {
- c.Timeout = ival
+ // Check for device commits.
+ all_done := true
+ for _, d := range ans.Devices {
+ // log %q d.Serial %s d.Result
+ if d.Result == "PEND" {
+ all_done = false
+ break
+ } else if d.Result != "OK" && all_ok {
+ all_ok = false
}
- } else if json_client.Timeout != 0 {
- c.Timeout = json_client.Timeout
- } else {
- c.Timeout = 10
}
- }
- if c.Timeout <= 0 {
- return fmt.Errorf("Timeout for %q must be a positive int", c.Hostname)
- }
- tout = time.Duration(time.Duration(c.Timeout) * time.Second)
- // Target.
- if c.Target == "" {
- if val := os.Getenv("PANOS_TARGET"); c.CheckEnvironment && val != "" {
- c.Target = val
- } else {
- c.Target = json_client.Target
- }
- }
-
- // Headers.
- if len(c.Headers) == 0 {
- if val := os.Getenv("PANOS_HEADERS"); c.CheckEnvironment && val != "" {
- if err := json.Unmarshal([]byte(val), &c.Headers); err != nil {
- return err
- }
- }
- if len(c.Headers) == 0 && len(json_client.Headers) > 0 {
- c.Headers = make(map[string]string)
- for k, v := range json_client.Headers {
- c.Headers[k] = v
+ // Check if the job's done.
+ if ans.Progress == 100 {
+ if all_done {
+ break
+ } else if !dp {
+ // log waiting for %d devices len(ans.Devices)
+ dp = true
}
}
- }
- // Verify cert.
- if !c.VerifyCertificate {
- if val := os.Getenv("PANOS_VERIFY_CERTIFICATE"); c.CheckEnvironment && val != "" {
- if vcb, err := strconv.ParseBool(val); err != nil {
- return err
- } else if vcb {
- c.VerifyCertificate = vcb
- }
- }
- if !c.VerifyCertificate && json_client.VerifyCertificate {
- c.VerifyCertificate = json_client.VerifyCertificate
+ if sleep > 0 {
+ time.Sleep(sleep)
}
}
- // Logging.
- if c.Logging == 0 {
- var ll []string
- if val := os.Getenv("PANOS_LOGGING"); c.CheckEnvironment && val != "" {
- ll = strings.Split(val, ",")
- } else {
- ll = json_client.LoggingFromInitialize
- }
- if len(ll) > 0 {
- var lv uint32
- for _, x := range ll {
- switch x {
- case "quiet":
- lv |= LogQuiet
- case "action":
- lv |= LogAction
- case "query":
- lv |= LogQuery
- case "op":
- lv |= LogOp
- case "uid":
- lv |= LogUid
- case "xpath":
- lv |= LogXpath
- case "send":
- lv |= LogSend
- case "receive":
- lv |= LogReceive
- default:
- return fmt.Errorf("Unknown logging requested: %s", x)
- }
- }
- c.Logging = lv
+ if ans.Result == "FAIL" {
+ if len(ans.Details.Lines) > 0 {
+ return fmt.Errorf(ans.Details.String())
} else {
- c.Logging = LogAction | LogUid
- }
- }
-
- // Setup the https client.
- if c.Transport == nil {
- c.Transport = &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: !c.VerifyCertificate,
- },
- }
- }
- c.con = &http.Client{
- Transport: c.Transport,
- Timeout: tout,
- }
-
- // Sanity check.
- if c.Hostname == "" {
- return fmt.Errorf("No hostname specified")
- } else if c.ApiKey == "" && (c.Username == "" && c.Password == "") {
- return fmt.Errorf("No username/password or API key given")
- }
-
- // Configure the api url
- if c.Port == 0 {
- c.api_url = fmt.Sprintf("%s://%s/api", c.Protocol, c.Hostname)
- } else {
- c.api_url = fmt.Sprintf("%s://%s:%d/api", c.Protocol, c.Hostname, c.Port)
- }
-
- return nil
-}
-
-func (c *Client) initApiKey() error {
- if c.ApiKey != "" {
- return nil
- }
-
- return c.RetrieveApiKey()
-}
-
-func (c *Client) initSystemInfo() error {
- var err error
- c.LogOp("(op) show system info")
-
- // Run "show system info"
- type system_info_req struct {
- XMLName xml.Name `xml:"show"`
- Cmd string `xml:"system>info"`
- }
-
- type tagVal struct {
- XMLName xml.Name
- Value string `xml:",chardata"`
- }
-
- type sysTag struct {
- XMLName xml.Name `xml:"system"`
- Tag []tagVal `xml:",any"`
- }
-
- type system_info_ans struct {
- System sysTag `xml:"result>system"`
- }
-
- req := system_info_req{}
- ans := system_info_ans{}
-
- _, err = c.Op(req, "", nil, &ans)
- if err != nil {
- return err
- }
-
- c.SystemInfo = make(map[string]string, len(ans.System.Tag))
- for i := range ans.System.Tag {
- c.SystemInfo[ans.System.Tag[i].XMLName.Local] = ans.System.Tag[i].Value
- if ans.System.Tag[i].XMLName.Local == "sw-version" {
- c.Version, err = version.New(ans.System.Tag[i].Value)
- if err != nil {
- return fmt.Errorf("Error parsing version %s: %s", ans.System.Tag[i].Value, err)
- }
+ return fmt.Errorf("Job %d has failed", id)
}
+ } else if !all_ok {
+ return fmt.Errorf("Commit failed on one or more devices")
}
- return nil
-}
-
-func (c *Client) initPlugins() {
- c.LogOp("(op) getting plugin info")
-
- var req plugin.GetPlugins
- var ans plugin.PackageListing
-
- if _, err := c.Op(req, "", nil, &ans); err != nil {
- c.LogAction("WARNING: Failed to get plugin info: %s", err)
- return
+ if resp == nil {
+ return nil
}
- c.Plugin = ans.Listing()
+ return xml.Unmarshal(data, resp)
}
-func (c *Client) typeConfig(action string, data url.Values, element, extras, ans interface{}) ([]byte, error) {
- var err error
-
- if c.MultiConfigure != nil && (action == "set" ||
- action == "edit" ||
- action == "delete") {
- r := MultiConfigureRequest{
- XMLName: xml.Name{Local: action},
- Xpath: data.Get("xpath"),
- }
- if element != nil {
- r.Data = element
- }
- c.MultiConfigure.Reqs = append(c.MultiConfigure.Reqs, r)
- return nil, nil
+// RevertToRunningConfig discards any changes made and reverts to the last
+// committed config.
+func (c *Client) RevertToRunningConfig(ctx context.Context, vsys string) error {
+ type req struct {
+ XMLName xml.Name `xml:"load"`
+ Cmd string `xml:"config>from"`
}
- data.Set("type", "config")
- data.Set("action", action)
-
- if element != nil {
- if err = addToData("element", element, true, &data); err != nil {
- return nil, err
- }
+ cmd := &xmlapi.Op{
+ Command: req{
+ Cmd: "running-config.xml",
+ },
+ Vsys: vsys,
+ Target: c.Target,
}
- if c.Target != "" {
- data.Set("target", c.Target)
- }
+ _, _, err := c.Communicate(ctx, cmd, false, nil)
+ return err
+}
- if err = mergeUrlValues(&data, extras); err != nil {
- return nil, err
+// StartJob sends the given command, which starts a job on PAN-OS.
+//
+// The uint returned is the job ID.
+func (c *Client) StartJob(ctx context.Context, cmd util.PangoCommand) (uint, []byte, *http.Response, error) {
+ var ans util.JobResponse
+ b, resp, err := c.Communicate(ctx, cmd, false, &ans)
+ if err != nil {
+ return 0, b, resp, err
}
- b, _, err := c.Communicate(data, ans)
- return b, err
+ return ans.Id, b, resp, nil
}
-func (c *Client) logXpath(p string) {
- if c.Logging&LogXpath == LogXpath {
- log.Printf("(xpath) %s", p)
+// Communicate sends the given content to PAN-OS.
+//
+// The API key is sent either in the request body or as a header.
+//
+// The timeout for the operation is taken from the context.
+func (c *Client) Communicate(ctx context.Context, cmd util.PangoCommand, strip bool, ans any) ([]byte, *http.Response, error) {
+ if cmd == nil {
+ return nil, nil, fmt.Errorf("cmd is nil")
}
-}
-// VsysImport imports the given names into the specified template / vsys.
-func (c *Client) VsysImport(loc, tmpl, ts, vsys string, names []string) error {
- path := c.xpathImport(tmpl, ts, vsys)
- if len(names) == 0 || vsys == "" {
- return nil
- } else if len(names) == 1 {
- path = append(path, loc)
+ data, err := cmd.AsUrlValues()
+ if err != nil {
+ return nil, nil, err
}
- obj := util.BulkElement{XMLName: xml.Name{Local: loc}}
- for i := range names {
- obj.Data = append(obj.Data, vis{xml.Name{Local: "member"}, names[i]})
+ if c.ApiKeyInRequest && c.ApiKey != "" && data.Get("key") == "" {
+ data.Set("key", c.ApiKey)
}
- _, err := c.Set(path, obj.Config(), nil, nil)
- return err
-}
+ c.logSend(data)
-// VsysUnimport removes the given names from all (template, optional) vsys.
-func (c *Client) VsysUnimport(loc, tmpl, ts string, names []string) error {
- if len(names) == 0 {
- return nil
+ req, err := http.NewRequestWithContext(ctx, "POST", c.api_url, strings.NewReader(data.Encode()))
+ if err != nil {
+ return nil, nil, err
}
- path := make([]string, 0, 14)
- path = append(path, c.xpathImport(tmpl, ts, "")...)
- path = append(path, loc, util.AsMemberXpath(names))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err := c.Delete(path, nil, nil)
- if err != nil {
- e2, ok := err.(errors.Panos)
- if ok && e2.ObjectNotFound() {
- return nil
- }
- }
- return err
+ return c.sendRequest(ctx, req, strip, ans)
}
-// IsImported checks if the importable object is actually imported in the
-// specified location.
-func (c *Client) IsImported(loc, tmpl, ts, vsys, name string) (bool, error) {
- path := make([]string, 0, 14)
- path = append(path, c.xpathImport(tmpl, ts, vsys)...)
- path = append(path, loc, util.AsMemberXpath([]string{name}))
-
- _, err := c.Get(path, nil, nil)
- if err == nil {
- if vsys != "" {
- return true, nil
- } else {
- return false, nil
- }
+// ImportFile imports the given file into PAN-OS.
+func (c *Client) ImportFile(ctx context.Context, cmd *xmlapi.Import, content, filename, fp string, strip bool, ans any) ([]byte, *http.Response, error) {
+ if cmd == nil {
+ return nil, nil, fmt.Errorf("cmd is nil")
}
- e2, ok := err.(errors.Panos)
- if ok && e2.ObjectNotFound() {
- if vsys != "" {
- return false, nil
- } else {
- return true, nil
- }
+ data, err := cmd.AsUrlValues()
+ if err != nil {
+ return nil, nil, err
}
- return false, err
-}
+ if c.ApiKeyInRequest && c.ApiKey != "" && data.Get("key") == "" {
+ data.Set("key", c.ApiKey)
+ }
-func (c *Client) xpathImport(tmpl, ts, vsys string) []string {
- ans := make([]string, 0, 12)
- if tmpl != "" || ts != "" {
- ans = append(ans, util.TemplateXpathPrefix(tmpl, ts)...)
- }
- ans = append(ans,
- "config",
- "devices",
- util.AsEntryXpath([]string{"localhost.localdomain"}),
- "vsys",
- util.AsEntryXpath([]string{vsys}),
- "import",
- "network",
- )
-
- return ans
-}
+ c.logSend(data)
-func (c *Client) post(data url.Values) ([]byte, http.Header, error) {
- if len(c.rb) == 0 {
- req, err := http.NewRequest("POST", c.api_url, strings.NewReader(data.Encode()))
- if err != nil {
- return nil, nil, err
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- for k, v := range c.Headers {
- req.Header.Set(k, v)
- }
+ buf := bytes.Buffer{}
+ w := multipart.NewWriter(&buf)
- r, err := c.con.Do(req)
+ for k := range data {
+ err = w.WriteField(k, data.Get(k))
if err != nil {
return nil, nil, err
}
-
- defer r.Body.Close()
- ans, err := ioutil.ReadAll(r.Body)
- return ans, r.Header, err
- } else {
- if c.ri < len(c.rb) {
- c.rp = append(c.rp, data)
- }
- body := c.rb[c.ri%len(c.rb)]
- var hdr http.Header
- if len(c.rh) > 0 {
- hdr = c.rh[c.ri%len(c.rh)]
- }
- c.ri++
- return body, hdr, nil
}
-}
-
-func (c *Client) endCommunication(body []byte, ans interface{}) error {
- var err error
- if c.Logging&LogReceive == LogReceive {
- log.Printf("Response = %s", body)
+ w2, err := w.CreateFormFile(fp, filename)
+ if err != nil {
+ return nil, nil, err
}
- // Check for errors first
- if err = errors.Parse(body); err != nil {
- return err
+ if _, err = io.Copy(w2, strings.NewReader(content)); err != nil {
+ return nil, nil, err
}
- // Return the body string if we weren't given something to unmarshal into
- if ans == nil {
- return nil
- }
+ w.Close()
- // Unmarshal using the struct passed in
- err = xml.Unmarshal(body, ans)
+ req, err := http.NewRequestWithContext(ctx, "POST", c.api_url, &buf)
if err != nil {
- return fmt.Errorf("Error unmarshaling into provided interface: %s", err)
- }
-
- return nil
-}
-
-/*
-PositionFirstEntity moves an element before another one using the Move API command.
-
-Param `mvt` is a util.Move* constant.
-
-Param `rel` is the relative entity that `mvt` is in relation to.
-
-Param `ent` is the entity that is to be positioned.
-
-Param `path` is the XPATH of `ent`.
-
-Param `elms` is the ordered list of entities that should include both
-`rel` and `ent`.
-be found.
-*/
-func (c *Client) PositionFirstEntity(mvt int, rel, ent string, path, elms []string) error {
- // Sanity checks.
- if rel == ent {
- return fmt.Errorf("Can't position %q in relation to itself", rel)
- } else if mvt < util.MoveSkip && mvt > util.MoveBottom {
- return fmt.Errorf("Invalid position int given: %d", mvt)
- } else if (mvt == util.MoveBefore || mvt == util.MoveDirectlyBefore || mvt == util.MoveAfter || mvt == util.MoveDirectlyAfter) && rel == "" {
- return fmt.Errorf("Specify 'ref' in order to perform relative group positioning")
+ return nil, nil, err
}
- var err error
- fIdx := -1
- oIdx := -1
-
- switch mvt {
- case util.MoveSkip:
- return nil
- case util.MoveTop:
- _, em := c.Move(path, "top", "", nil, nil)
- if em != nil && em.Error() != "already at the top" {
- err = em
- }
- case util.MoveBottom:
- _, em := c.Move(path, "bottom", "", nil, nil)
- if em != nil && em.Error() != "already at the bottom" {
- err = em
- }
- default:
- // Find the indexes of the first rule and the ref rule.
- for i, v := range elms {
- if v == ent {
- fIdx = i
- } else if v == rel {
- oIdx = i
- }
- if fIdx != -1 && oIdx != -1 {
- break
- }
- }
-
- // Sanity check: both rules should be present.
- if fIdx == -1 {
- return fmt.Errorf("Entity to be moved %q does not exist", ent)
- } else if oIdx == -1 {
- return fmt.Errorf("Reference entity %q does not exist", rel)
- }
-
- // Move the first element, if needed.
- if (mvt == util.MoveBefore && fIdx > oIdx) || (mvt == util.MoveDirectlyBefore && fIdx+1 != oIdx) {
- _, err = c.Move(path, "before", rel, nil, nil)
- } else if (mvt == util.MoveAfter && fIdx < oIdx) || (mvt == util.MoveDirectlyAfter && fIdx != oIdx+1) {
- _, err = c.Move(path, "after", rel, nil, nil)
- }
- }
+ req.Header.Set("Content-Type", w.FormDataContentType())
- return err
+ return c.sendRequest(ctx, req, strip, ans)
}
-// Clock gets the time on the PAN-OS appliance.
-func (c *Client) Clock() (time.Time, error) {
- type t_req struct {
- XMLName xml.Name `xml:"show"`
- Cmd string `xml:"clock"`
+// ExportFile retrieves a file from PAN-OS.
+func (c *Client) ExportFile(ctx context.Context, cmd *xmlapi.Export, ans any) (string, []byte, *http.Response, error) {
+ if cmd == nil {
+ return "", nil, nil, fmt.Errorf("cmd is nil")
}
- type t_resp struct {
- Result string `xml:"result"`
+ data, err := cmd.AsUrlValues()
+ if err != nil {
+ return "", nil, nil, err
}
- req := t_req{}
- ans := t_resp{}
-
- c.LogOp("(op) getting system time")
- if _, err := c.Op(req, "", nil, &ans); err != nil {
- return time.Time{}, err
+ if c.ApiKeyInRequest && c.ApiKey != "" && data.Get("key") == "" {
+ data.Set("key", c.ApiKey)
}
- return time.Parse(time.UnixDate+"\n", ans.Result)
-}
-
-// PrepareMultiConfigure will start a multi config command.
-//
-// Capacity is the initial capacity of the requests to be sent.
-func (c *Client) PrepareMultiConfigure(capacity int) {
- c.MultiConfigure = &MultiConfigure{
- Reqs: make([]MultiConfigureRequest, 0, capacity),
+ req, err := http.NewRequestWithContext(ctx, "POST", c.api_url, strings.NewReader(data.Encode()))
+ if err != nil {
+ return "", nil, nil, err
}
-}
-// SendMultiConfigure will send the accumulated multi configure request.
-//
-// Param strict should be true if you want strict transactional support.
-//
-// Note that the error returned from this function is only if there was an error
-// unmarshaling the response into the the multi config response struct. If the
-// multi config itself failed, then the reason can be found in its results.
-func (c *Client) SendMultiConfigure(strict bool) (MultiConfigureResponse, error) {
- if c.MultiConfigure == nil {
- return MultiConfigureResponse{}, nil
+ b, resp, err := c.sendRequest(ctx, req, false, ans)
+ if err != nil {
+ return "", b, resp, err
}
- mc := c.MultiConfigure
- c.MultiConfigure = nil
+ var filename string
+ mediatype, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
+ if err == nil && mediatype == "attachment" {
+ filename = params["filename"]
+ }
- _, ans, err := c.MultiConfig(*mc, strict, nil)
- return ans, err
+ return filename, b, resp, nil
}
-// GetTechSupportFile returns the tech support .tgz file.
-//
-// This function returns the name of the tech support file, the file
-// contents, and an error if one occurred.
-//
-// The timeout param is the new timeout (in seconds) to temporarily assign to
-// client connections to allow for the successful download of the tech support
-// file. If the timeout is zero, then pango.Client.Timeout is the timeout for
-// tech support file retrieval.
-func (c *Client) GetTechSupportFile(timeout time.Duration) (string, []byte, error) {
- if timeout < 0 {
- return "", nil, fmt.Errorf("timeout cannot be negative")
+// GetTechSupportFile returns the tech support file .tgz file.
+func (c *Client) GetTechSupportFile(ctx context.Context) (string, []byte, error) {
+ cmd := &xmlapi.Export{
+ Category: "tech-support",
}
- var err error
- var resp util.JobResponse
- cmd := "tech-support"
-
- c.LogExport("(export) tech support file")
-
- // Request the tech support file creation.
- _, _, err = c.Export(cmd, 0, nil, &resp)
+ id, _, _, err := c.StartJob(ctx, cmd)
if err != nil {
return "", nil, err
}
- if resp.Id == 0 {
- return "", nil, fmt.Errorf("Job ID was not found")
- }
- extras := url.Values{}
- extras.Set("action", "status")
- extras.Set("job-id", fmt.Sprintf("%d", resp.Id))
+ cmd.Action = "status"
+ cmd.JobId = id
- // Poll the job until it's done.
- var pr util.BasicJob
+ var resp util.BasicJob
var prev uint
for {
- _, _, err = c.Export(cmd, 0, extras, &pr)
- if err != nil {
+ resp = util.BasicJob{}
+
+ if _, _, _, err = c.ExportFile(ctx, cmd, &resp); err != nil {
return "", nil, err
}
// The progress is not an uint when the job completes, so don't print
// the progress as 0 when the job is actually complete.
- if pr.Progress != prev && pr.Progress != 0 {
- prev = pr.Progress
- c.LogExport("(export) tech support job %d: %d percent complete", resp.Id, prev)
+ if resp.Progress != prev && resp.Progress != 0 {
+ prev = resp.Progress
+ // log %d resp.Id at %d prev percentage complete.
}
- if pr.Status == "FIN" {
+ if resp.Status == "FIN" {
break
}
time.Sleep(2 * time.Second)
}
- if pr.Result == "FAIL" {
- return "", nil, fmt.Errorf(pr.Details.String())
+ if resp.Result == "FAIL" {
+ return "", nil, fmt.Errorf(resp.Details.String())
}
- extras.Set("action", "get")
- return c.Export(cmd, timeout, extras, nil)
+ cmd.Action = "get"
+
+ filename, b, _, err := c.ExportFile(ctx, cmd, nil)
+ return filename, b, err
}
-// RetrievePanosConfig retrieves either the running config, candidate config,
-// or the specified saved config file, then does `LoadPanosConfig()` to save it.
//
-// After the config is loaded, config can be queried and retrieved using
-// any `FromPanosConfig()` methods.
+// Internal functions.
//
-// Param `value` can be the word "candidate" to load candidate config or
-// `running` to load running config. If the value is neither of those, it
-// is assumed to be the name of a saved config and that is loaded.
-func (c *Client) RetrievePanosConfig(value string) error {
- type getConfig struct {
- XMLName xml.Name `xml:"show"`
- Running *string `xml:"config>running"`
- Candidate *string `xml:"config>candidate"`
- Saved *string `xml:"config>saved"`
+
+func (c *Client) setupLogging(logging LoggingInfo) error {
+ var logger *slog.Logger
+
+ if logging.SLogHandler == nil {
+ logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logging.LogLevel}))
+ logger.Info("No slog handler provided, creating default os.Stderr handler.", "LogLevel", logging.LogLevel.Level())
+ } else {
+ logger = slog.New(logging.SLogHandler)
+ if logging.LogLevel != 0 {
+ logger.Warn("LogLevel is ignored when using custom SLog handler.")
+ }
}
- type data struct {
- Data []byte `xml:",innerxml"`
+ // 1. logging.LogCategories has the highest priority
+ // 2. If logging.LogCategories is not set, we check logging.LogSymbols
+ // 3. If logging.LogSymbols is empty and c.CheckEnvironment is true we consult
+ // PANOS_LOGGING environment variable.
+ // 4. If no logging categories have been selected, default to basic library logging
+ // (i.e. "pango" category)
+ logMask := logging.LogCategories
+ var err error
+ if logMask == 0 {
+ logMask, err = LogCategoryFromStrings(logging.LogSymbols)
+ if err != nil {
+ return err
+ }
+
+ if logMask == 0 {
+ if val := os.Getenv("PANOS_LOGGING"); c.CheckEnvironment && val != "" {
+ symbols := strings.Split(val, ",")
+ logMask, err = LogCategoryFromStrings(symbols)
+ if err != nil {
+ return err
+ }
+ }
+ }
}
- type resp struct {
- XMLName xml.Name `xml:"response"`
- Result data `xml:"result"`
+ // To disable logging completely, use custom SLog handler that discards all logs,
+ // e.g. https://github.com/golang/go/issues/62005 for the example of such handler.
+ if logMask == 0 {
+ logMask = LogCategoryPango
}
- s := ""
- req := getConfig{}
- switch value {
- case "candidate":
- req.Candidate = &s
- case "running":
- req.Running = &s
- default:
- req.Saved = &value
+ enabledLogging, _ := LogCategoryAsStrings(logMask)
+ logger.Info("Pango logging configured", "symbols", enabledLogging)
+
+ c.logger = newCategoryLogger(logger, logMask)
+
+ return nil
+}
+
+func (c *Client) sendRequest(ctx context.Context, req *http.Request, strip bool, ans any) ([]byte, *http.Response, error) {
+ // Optional: set the API key in the header.
+ if !c.ApiKeyInRequest && c.ApiKey != "" {
+ req.Header.Set("X-PAN-KEY", c.ApiKey)
}
- ans := resp{}
- if _, err := c.Op(req, "", nil, &ans); err != nil {
- return err
+ // Configure all user given headers.
+ for k, v := range c.Headers {
+ req.Header.Set(k, v)
}
- return c.LoadPanosConfig(ans.Result.Data)
-}
+ // Perform the operation.
+ var err error
+ var resp *http.Response
+ if len(c.testOutput) != 0 {
+ c.testInput = append(c.testInput, req)
+ resp = c.testOutput[c.testIndex%len(c.testOutput)]
+ c.testIndex++
+ } else {
+ resp, err = c.con.Do(req)
+ }
-// LoadPanosConfig stores the given XML document into the local client instance.
-//
-// The `config` can either be `...` or something that contians
-// only the config document (such as `...`).
-//
-// After the config is loaded, config can be queried and retrieved using
-// any `FromPanosConfig()` methods.
-func (c *Client) LoadPanosConfig(config []byte) error {
- log.Printf("load panos config")
- if err := xml.Unmarshal(config, &c.configTree); err != nil {
- return err
+ if err != nil {
+ return nil, nil, err
}
- if c.configTree.XMLName.Local == "config" {
- // Add a place holder parent util.XmlNode.
- c.configTree = &util.XmlNode{
- XMLName: xml.Name{
- Local: "a",
- },
- Nodes: []util.XmlNode{
- *c.configTree,
- },
+ // Read in the response.
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return body, resp, err
+ }
+
+ // Check the response for errors.
+ if err = errors.Parse(body); err != nil {
+ return body, resp, err
+ }
+
+ // Optional: strip the "response" tag from the XML returned.
+ if strip {
+ var index int
+ gt := []byte(">")
+ lt := []byte("<")
+
+ index = bytes.Index(body, gt)
+ if index > 0 {
+ body = body[index+1:]
+ index = bytes.LastIndex(body, lt)
+ if index > 0 {
+ body = body[:index]
+ }
}
- return nil
}
- if len(c.configTree.Nodes) == 1 && c.configTree.Nodes[0].XMLName.Local == "config" {
- // Already has a place holder parent.
- return nil
+ if ans == nil {
+ return body, resp, nil
}
- c.configTree = nil
- return fmt.Errorf("doesn't seem to be a config tree")
-}
+ // Optional: unmarshal using the struct passed in.
+ err = xml.Unmarshal(body, ans)
+ if err != nil {
+ return body, resp, fmt.Errorf("err unmarshaling into provided interface: %s", err)
+ }
+
+ c.logger.WithLogCategory(LogCategoryReceive).Debug("Received data from server", "body", c.prepareReceiveDataForLogging(body))
-// ConfigTree returns the configuration tree that was loaded either via
-// `RetrievePanosConfig()` or `LoadPanosConfig()`.
-func (c *Client) ConfigTree() *util.XmlNode {
- return c.configTree
+ return body, resp, nil
}
func (c *Client) logSend(data url.Values) {
- var b strings.Builder
-
- // Traditional send logging.
- if c.Logging&LogSend == LogSend {
- if b.Len() > 0 {
- fmt.Fprintf(&b, "\n")
- }
- realKey := data.Get("key")
- if realKey != "" {
- data.Set("key", "########")
- }
- fmt.Fprintf(&b, "Sending data: %#v", data)
- if realKey != "" {
- data.Set("key", realKey)
- }
+ c.logger.WithLogCategory(LogCategoryPango).Debug("Hello World!")
+ sendData := slog.Group("data", c.prepareSendDataForLogging(data)...)
+ if c.logger.enabledFor(LogCategoryCurl) {
+ curlEquivalent := slog.Group("curl", c.prepareSendDataAsCurl(data)...)
+ c.logger.WithLogCategory(LogCategorySend).Debug("data sent to the server", sendData, curlEquivalent)
+ } else {
+ c.logger.WithLogCategory(LogCategorySend).Debug("data sent to the server", sendData)
}
+}
- // Log the send data as an OSX curl command.
- if c.Logging&LogOsxCurl == LogOsxCurl {
- if b.Len() > 0 {
- fmt.Fprintf(&b, "\n")
- }
- special := map[string]string{
- "key": "",
- "element": "",
- }
- ev := url.Values{}
- for k := range data {
- var isSpecial bool
- for sk := range special {
- if sk == k {
- isSpecial = true
- special[k] = data.Get(k)
- break
- }
- }
- if !isSpecial {
- ev[k] = make([]string, 0, len(data[k]))
- for i := range data[k] {
- ev[k] = append(ev[k], data[k][i])
- }
- }
- }
+func (c *Client) prepareSendDataForLogging(data url.Values) []any {
+ filterSensitive := !c.logger.enabledFor(LogCategorySensitive)
- // Build up the curl command.
- fmt.Fprintf(&b, "curl")
- // Verify cert.
- if !c.VerifyCertificate {
- fmt.Fprintf(&b, " -k")
- }
- // Headers.
- if len(c.Headers) > 0 && c.Logging&LogCurlWithPersonalData == LogCurlWithPersonalData {
- for k, v := range c.Headers {
- if v != "" {
- fmt.Fprintf(&b, " --header '%s: %s'", k, v)
- } else {
- fmt.Fprintf(&b, " --header '%s;'", k)
+ preparedValues := make([]any, 0)
+
+ for key, values := range data {
+ for _, value := range values {
+ preparedValues = append(preparedValues, key)
+ if filterSensitive {
+ switch key {
+ case "key", "password":
+ preparedValues = append(preparedValues, "***")
+ default:
+ preparedValues = append(preparedValues, value)
}
- }
- }
- // Add URL encoded values.
- if special["key"] != "" {
- if c.Logging&LogCurlWithPersonalData == LogCurlWithPersonalData {
- ev.Set("key", special["key"])
} else {
- ev.Set("key", "APIKEY")
+ preparedValues = append(preparedValues, value)
}
}
- // Add in the element, if present.
- if special["element"] != "" {
- fmt.Fprintf(&b, " --data-urlencode element@element.xml")
- }
- // URL.
- fmt.Fprintf(&b, " '%s://", c.Protocol)
- if c.Logging&LogCurlWithPersonalData == LogCurlWithPersonalData {
- fmt.Fprintf(&b, "%s", c.Hostname)
- } else {
- fmt.Fprintf(&b, "HOST")
- }
- if c.Port != 0 {
- fmt.Fprintf(&b, ":%d", c.Port)
- }
- fmt.Fprintf(&b, "/api")
- if len(ev) > 0 {
- fmt.Fprintf(&b, "?%s", ev.Encode())
- }
- fmt.Fprintf(&b, "'")
- // Data.
- if special["element"] != "" {
- fmt.Fprintf(&b, "\nelement.xml:\n%s", special["element"])
- }
}
- if b.Len() > 0 {
- log.Printf("%s", b.String())
- }
+ return preparedValues
}
-/** Non-struct private functions **/
+func (c *Client) prepareSendDataAsCurl(data url.Values) []any {
+ filterSensitive := !c.logger.enabledFor(LogCategorySensitive)
-func mergeUrlValues(data *url.Values, extras interface{}) error {
- if extras == nil {
- return nil
+ // A map of items and their values for items that require additional
+ // processing, and not be sent as is.
+ special := map[string]string{
+ "element": "",
}
- ev, ok := extras.(url.Values)
- if !ok {
- return fmt.Errorf("extras needs to be of type url.Values or nil")
- }
+ sensitive := []string{"user", "password", "key", "host"}
- for key := range ev {
- data.Set(key, ev.Get(key))
- }
+ values := url.Values{}
+ for key, value := range data {
+ isSpecial := false
+ for needle := range special {
+ if key == needle {
+ isSpecial = true
+ special[key] = value[0]
+ break
+ }
+ }
- return nil
-}
+ isSensitive := false
+ for _, needle := range sensitive {
+ if key == needle {
+ isSensitive = true
+ break
+ }
+ }
-func addToData(key string, i interface{}, attemptMarshal bool, data *url.Values) error {
- if i == nil {
- return nil
+ // Only non-special values should be part of the
+ // encoded url, and sensitive values should only be
+ // visible when LogCategorySensitiveis set.
+ if !isSpecial {
+ if isSensitive && filterSensitive {
+ values[key] = []string{strings.ToUpper(key)}
+ } else {
+ values[key] = value
+ }
+ }
}
- val, err := asString(i, attemptMarshal)
- if err != nil {
- return err
+ curlCmd := []string{"curl"}
+
+ if c.SkipVerifyCertificate {
+ curlCmd = append(curlCmd, "-k")
}
- data.Set(key, val)
- return nil
-}
+ if len(c.Headers) > 0 && !filterSensitive {
+ for k, v := range c.Headers {
+ curlCmd = append(curlCmd, "--header")
+ var header string
+ if v == "" {
+ header = fmt.Sprintf("'%s;'", k)
+ } else {
+ header = fmt.Sprintf("'%s: %s'", k, v)
+ }
+ curlCmd = append(curlCmd, header)
+ }
+ }
-func asString(i interface{}, attemptMarshal bool) (string, error) {
- if a, ok := i.(fmt.Stringer); ok {
- return a.String(), nil
+ hostname := c.Hostname
+ if filterSensitive {
+ hostname = "HOST"
+ }
+ port := ""
+ if c.Port != 0 {
+ port = fmt.Sprintf(":%d", c.Port)
}
- if b, ok := i.(util.Elementer); ok {
- i = b.Element()
+ encodedValues := ""
+ if len(values) > 0 {
+ encodedValues = fmt.Sprintf("?%s", values.Encode())
}
- switch val := i.(type) {
- case nil:
- return "", fmt.Errorf("nil encountered")
- case string:
- return val, nil
- default:
- if !attemptMarshal {
- return "", fmt.Errorf("value must be string or fmt.Stringer")
- }
+ urlTemplate := "'%s://%s%s/api%s'"
+ apiUrl := fmt.Sprintf(urlTemplate, c.Protocol, hostname, port, encodedValues)
- rb, err := xml.Marshal(val)
- if err != nil {
- return "", err
- }
- return string(rb), nil
- }
-}
+ curlCmd = append(curlCmd, apiUrl)
-// vis is a vsys import struct.
-type vis struct {
- XMLName xml.Name
- Text string `xml:",chardata"`
-}
+ curlData := []any{"command", strings.Join(curlCmd, " ")}
+
+ if special["element"] != "" {
+ curlData = append(curlData, "element.xml")
+ curlData = append(curlData, special["element"])
+ }
-type configLocks struct {
- Locks []util.Lock `xml:"result>config-locks>entry"`
+ return curlData
}
-type commitLocks struct {
- Locks []util.Lock `xml:"result>commit-locks>entry"`
+func (c *Client) prepareReceiveDataForLogging(body []byte) string {
+ replacedBody := string(body)
+ keyStartIdx := strings.Index(replacedBody, "")
+ keyEndIdx := strings.Index(replacedBody, "")
+ if keyStartIdx != -1 && keyEndIdx != -1 {
+ replacedBody = replacedBody[:keyStartIdx] + "***" + replacedBody[keyEndIdx+len(""):]
+ }
+ return replacedBody
}
diff --git a/device/services/dns/config.go b/device/services/dns/config.go
new file mode 100644
index 0000000..4e04f76
--- /dev/null
+++ b/device/services/dns/config.go
@@ -0,0 +1,158 @@
+package dns
+
+import (
+ "encoding/xml"
+
+ "github.com/PaloAltoNetworks/pango/generic"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type Config struct {
+ DnsSetting *DnsSetting
+ FqdnRefreshTime *int64
+
+ Misc map[string][]generic.Xml
+}
+type DnsSetting struct {
+ Servers *DnsSettingServers
+}
+type DnsSettingServers struct {
+ Primary *string
+ Secondary *string
+}
+type configXmlContainer struct {
+ XMLName xml.Name `xml:"result"`
+ Answer []configXml `xml:"system"`
+}
+type configXml struct {
+ XMLName xml.Name `xml:"system"`
+ DnsSetting *DnsSettingXml `xml:"dns-setting,omitempty"`
+ FqdnRefreshTime *int64 `xml:"fqdn-refresh-time,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type DnsSettingXml struct {
+ Servers *DnsSettingServersXml `xml:"servers,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type DnsSettingServersXml struct {
+ Primary *string `xml:"primary,omitempty"`
+ Secondary *string `xml:"secondary,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyConfig, &configXmlContainer{}, nil
+}
+func specifyConfig(o *Config) (any, error) {
+ config := configXml{}
+ var nestedDnsSetting *DnsSettingXml
+ if o.DnsSetting != nil {
+ nestedDnsSetting = &DnsSettingXml{}
+ if _, ok := o.Misc["DnsSetting"]; ok {
+ nestedDnsSetting.Misc = o.Misc["DnsSetting"]
+ }
+ if o.DnsSetting.Servers != nil {
+ nestedDnsSetting.Servers = &DnsSettingServersXml{}
+ if _, ok := o.Misc["DnsSettingServers"]; ok {
+ nestedDnsSetting.Servers.Misc = o.Misc["DnsSettingServers"]
+ }
+ if o.DnsSetting.Servers.Primary != nil {
+ nestedDnsSetting.Servers.Primary = o.DnsSetting.Servers.Primary
+ }
+ if o.DnsSetting.Servers.Secondary != nil {
+ nestedDnsSetting.Servers.Secondary = o.DnsSetting.Servers.Secondary
+ }
+ }
+ }
+ config.DnsSetting = nestedDnsSetting
+
+ config.FqdnRefreshTime = o.FqdnRefreshTime
+
+ config.Misc = o.Misc["Config"]
+
+ return config, nil
+}
+func (c *configXmlContainer) Normalize() ([]*Config, error) {
+ configList := make([]*Config, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ config := &Config{
+ Misc: make(map[string][]generic.Xml),
+ }
+ var nestedDnsSetting *DnsSetting
+ if o.DnsSetting != nil {
+ nestedDnsSetting = &DnsSetting{}
+ if o.DnsSetting.Misc != nil {
+ config.Misc["DnsSetting"] = o.DnsSetting.Misc
+ }
+ if o.DnsSetting.Servers != nil {
+ nestedDnsSetting.Servers = &DnsSettingServers{}
+ if o.DnsSetting.Servers.Misc != nil {
+ config.Misc["DnsSettingServers"] = o.DnsSetting.Servers.Misc
+ }
+ if o.DnsSetting.Servers.Primary != nil {
+ nestedDnsSetting.Servers.Primary = o.DnsSetting.Servers.Primary
+ }
+ if o.DnsSetting.Servers.Secondary != nil {
+ nestedDnsSetting.Servers.Secondary = o.DnsSetting.Servers.Secondary
+ }
+ }
+ }
+ config.DnsSetting = nestedDnsSetting
+
+ config.FqdnRefreshTime = o.FqdnRefreshTime
+
+ config.Misc["Config"] = o.Misc
+
+ configList = append(configList, config)
+ }
+
+ return configList, nil
+}
+
+func SpecMatches(a, b *Config) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchDnsSetting(a.DnsSetting, b.DnsSetting) {
+ return false
+ }
+ if !util.Ints64Match(a.FqdnRefreshTime, b.FqdnRefreshTime) {
+ return false
+ }
+
+ return true
+}
+
+func matchDnsSettingServers(a *DnsSettingServers, b *DnsSettingServers) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.Primary, b.Primary) {
+ return false
+ }
+ if !util.StringsMatch(a.Secondary, b.Secondary) {
+ return false
+ }
+ return true
+}
+func matchDnsSetting(a *DnsSetting, b *DnsSetting) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchDnsSettingServers(a.Servers, b.Servers) {
+ return false
+ }
+ return true
+}
diff --git a/device/services/dns/interfaces.go b/device/services/dns/interfaces.go
new file mode 100644
index 0000000..188f454
--- /dev/null
+++ b/device/services/dns/interfaces.go
@@ -0,0 +1,7 @@
+package dns
+
+type Specifier func(*Config) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Config, error)
+}
diff --git a/device/services/dns/location.go b/device/services/dns/location.go
new file mode 100644
index 0000000..4d42ef9
--- /dev/null
+++ b/device/services/dns/location.go
@@ -0,0 +1,180 @@
+package dns
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ System *SystemLocation `json:"system,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type SystemLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewSystemLocation() *Location {
+ return &Location{System: &SystemLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.System != nil:
+ if o.System.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.System != nil:
+ if o.System.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.System.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) Xpath(vn version.Number) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ return ans, nil
+}
diff --git a/device/services/dns/service.go b/device/services/dns/service.go
new file mode 100644
index 0000000..83ca11b
--- /dev/null
+++ b/device/services/dns/service.go
@@ -0,0 +1,175 @@
+package dns
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, config *Config) (*Config, error) {
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(config)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, action string) (*Config, error) {
+ return s.read(ctx, loc, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location) (*Config, error) {
+ return s.read(ctx, loc, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, action string, usePanosConfig bool) (*Config, error) {
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+func (s *Service) Update(ctx context.Context, loc Location, entry *Config) (*Config, error) {
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Config
+ old, err = s.Read(ctx, loc, "get")
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, config *Config) error {
+ return s.delete(ctx, loc, config)
+}
+func (s *Service) delete(ctx context.Context, loc Location, config *Config) error {
+
+ vn := s.client.Versioning()
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return err
+ }
+ deleteSuffixes := []string{}
+ deleteSuffixes = append(deleteSuffixes, "dns-setting")
+ deleteSuffixes = append(deleteSuffixes, "fqdn-refresh-time")
+
+ for _, suffix := range deleteSuffixes {
+ cmd := &xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(append(path, suffix)),
+ Target: s.client.GetTarget(),
+ }
+
+ _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/device/services/ntp/config.go b/device/services/ntp/config.go
new file mode 100644
index 0000000..59ee99c
--- /dev/null
+++ b/device/services/ntp/config.go
@@ -0,0 +1,538 @@
+package ntp
+
+import (
+ "encoding/xml"
+
+ "github.com/PaloAltoNetworks/pango/generic"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type Config struct {
+ NtpServers *NtpServers
+
+ Misc map[string][]generic.Xml
+}
+type NtpServers struct {
+ PrimaryNtpServer *NtpServersPrimaryNtpServer
+ SecondaryNtpServer *NtpServersSecondaryNtpServer
+}
+type NtpServersPrimaryNtpServer struct {
+ AuthenticationType *NtpServersPrimaryNtpServerAuthenticationType
+ NtpServerAddress *string
+}
+type NtpServersPrimaryNtpServerAuthenticationType struct {
+ Autokey *string
+ None *string
+ SymmetricKey *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey struct {
+ KeyId *int64
+ Md5 *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5
+ Sha1 *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5 struct {
+ AuthenticationKey *string
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1 struct {
+ AuthenticationKey *string
+}
+type NtpServersSecondaryNtpServer struct {
+ AuthenticationType *NtpServersSecondaryNtpServerAuthenticationType
+ NtpServerAddress *string
+}
+type NtpServersSecondaryNtpServerAuthenticationType struct {
+ Autokey *string
+ None *string
+ SymmetricKey *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey struct {
+ KeyId *int64
+ Md5 *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5
+ Sha1 *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5 struct {
+ AuthenticationKey *string
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1 struct {
+ AuthenticationKey *string
+}
+type configXmlContainer struct {
+ XMLName xml.Name `xml:"result"`
+ Answer []configXml `xml:"system"`
+}
+type configXml struct {
+ XMLName xml.Name `xml:"system"`
+ NtpServers *NtpServersXml `xml:"ntp-servers,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersXml struct {
+ PrimaryNtpServer *NtpServersPrimaryNtpServerXml `xml:"primary-ntp-server,omitempty"`
+ SecondaryNtpServer *NtpServersSecondaryNtpServerXml `xml:"secondary-ntp-server,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersPrimaryNtpServerXml struct {
+ AuthenticationType *NtpServersPrimaryNtpServerAuthenticationTypeXml `xml:"authentication-type,omitempty"`
+ NtpServerAddress *string `xml:"ntp-server-address,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeXml struct {
+ Autokey *string `xml:"autokey,omitempty"`
+ None *string `xml:"none,omitempty"`
+ SymmetricKey *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyXml `xml:"symmetric-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyXml struct {
+ KeyId *int64 `xml:"key-id,omitempty"`
+ Md5 *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml `xml:"algorithm>md5,omitempty"`
+ Sha1 *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1Xml `xml:"algorithm>sha1,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml struct {
+ AuthenticationKey *string `xml:"authentication-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1Xml struct {
+ AuthenticationKey *string `xml:"authentication-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersSecondaryNtpServerXml struct {
+ AuthenticationType *NtpServersSecondaryNtpServerAuthenticationTypeXml `xml:"authentication-type,omitempty"`
+ NtpServerAddress *string `xml:"ntp-server-address,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeXml struct {
+ Autokey *string `xml:"autokey,omitempty"`
+ None *string `xml:"none,omitempty"`
+ SymmetricKey *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyXml `xml:"symmetric-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyXml struct {
+ KeyId *int64 `xml:"key-id,omitempty"`
+ Md5 *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml `xml:"algorithm>md5,omitempty"`
+ Sha1 *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1Xml `xml:"algorithm>sha1,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml struct {
+ AuthenticationKey *string `xml:"authentication-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1Xml struct {
+ AuthenticationKey *string `xml:"authentication-key,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyConfig, &configXmlContainer{}, nil
+}
+func specifyConfig(o *Config) (any, error) {
+ config := configXml{}
+ var nestedNtpServers *NtpServersXml
+ if o.NtpServers != nil {
+ nestedNtpServers = &NtpServersXml{}
+ if _, ok := o.Misc["NtpServers"]; ok {
+ nestedNtpServers.Misc = o.Misc["NtpServers"]
+ }
+ if o.NtpServers.PrimaryNtpServer != nil {
+ nestedNtpServers.PrimaryNtpServer = &NtpServersPrimaryNtpServerXml{}
+ if _, ok := o.Misc["NtpServersPrimaryNtpServer"]; ok {
+ nestedNtpServers.PrimaryNtpServer.Misc = o.Misc["NtpServersPrimaryNtpServer"]
+ }
+ if o.NtpServers.PrimaryNtpServer.NtpServerAddress != nil {
+ nestedNtpServers.PrimaryNtpServer.NtpServerAddress = o.NtpServers.PrimaryNtpServer.NtpServerAddress
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType = &NtpServersPrimaryNtpServerAuthenticationTypeXml{}
+ if _, ok := o.Misc["NtpServersPrimaryNtpServerAuthenticationType"]; ok {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.Misc = o.Misc["NtpServersPrimaryNtpServerAuthenticationType"]
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.None != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.None = o.NtpServers.PrimaryNtpServer.AuthenticationType.None
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyXml{}
+ if _, ok := o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey"]; ok {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Misc = o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey"]
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5 != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5 = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml{}
+ if _, ok := o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5"]; ok {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc = o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5"]
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey
+ }
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1 != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1 = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1Xml{}
+ if _, ok := o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1"]; ok {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc = o.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1"]
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey
+ }
+ }
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.Autokey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.Autokey = o.NtpServers.PrimaryNtpServer.AuthenticationType.Autokey
+ }
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer != nil {
+ nestedNtpServers.SecondaryNtpServer = &NtpServersSecondaryNtpServerXml{}
+ if _, ok := o.Misc["NtpServersSecondaryNtpServer"]; ok {
+ nestedNtpServers.SecondaryNtpServer.Misc = o.Misc["NtpServersSecondaryNtpServer"]
+ }
+ if o.NtpServers.SecondaryNtpServer.NtpServerAddress != nil {
+ nestedNtpServers.SecondaryNtpServer.NtpServerAddress = o.NtpServers.SecondaryNtpServer.NtpServerAddress
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType = &NtpServersSecondaryNtpServerAuthenticationTypeXml{}
+ if _, ok := o.Misc["NtpServersSecondaryNtpServerAuthenticationType"]; ok {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.Misc = o.Misc["NtpServersSecondaryNtpServerAuthenticationType"]
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.None != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.None = o.NtpServers.SecondaryNtpServer.AuthenticationType.None
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyXml{}
+ if _, ok := o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey"]; ok {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Misc = o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey"]
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1 != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1 = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1Xml{}
+ if _, ok := o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1"]; ok {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc = o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1"]
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5 != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5 = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5Xml{}
+ if _, ok := o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5"]; ok {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc = o.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5"]
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey
+ }
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.Autokey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.Autokey = o.NtpServers.SecondaryNtpServer.AuthenticationType.Autokey
+ }
+ }
+ }
+ }
+ config.NtpServers = nestedNtpServers
+
+ config.Misc = o.Misc["Config"]
+
+ return config, nil
+}
+func (c *configXmlContainer) Normalize() ([]*Config, error) {
+ configList := make([]*Config, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ config := &Config{
+ Misc: make(map[string][]generic.Xml),
+ }
+ var nestedNtpServers *NtpServers
+ if o.NtpServers != nil {
+ nestedNtpServers = &NtpServers{}
+ if o.NtpServers.Misc != nil {
+ config.Misc["NtpServers"] = o.NtpServers.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer != nil {
+ nestedNtpServers.PrimaryNtpServer = &NtpServersPrimaryNtpServer{}
+ if o.NtpServers.PrimaryNtpServer.Misc != nil {
+ config.Misc["NtpServersPrimaryNtpServer"] = o.NtpServers.PrimaryNtpServer.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer.NtpServerAddress != nil {
+ nestedNtpServers.PrimaryNtpServer.NtpServerAddress = o.NtpServers.PrimaryNtpServer.NtpServerAddress
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType = &NtpServersPrimaryNtpServerAuthenticationType{}
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.Misc != nil {
+ config.Misc["NtpServersPrimaryNtpServerAuthenticationType"] = o.NtpServers.PrimaryNtpServer.AuthenticationType.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.None != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.None = o.NtpServers.PrimaryNtpServer.AuthenticationType.None
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey{}
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Misc != nil {
+ config.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey"] = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.KeyId
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1 != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1 = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1{}
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc != nil {
+ config.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1"] = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey
+ }
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5 != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5 = &NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5{}
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc != nil {
+ config.Misc["NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5"] = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey = o.NtpServers.PrimaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey
+ }
+ }
+ }
+ if o.NtpServers.PrimaryNtpServer.AuthenticationType.Autokey != nil {
+ nestedNtpServers.PrimaryNtpServer.AuthenticationType.Autokey = o.NtpServers.PrimaryNtpServer.AuthenticationType.Autokey
+ }
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer != nil {
+ nestedNtpServers.SecondaryNtpServer = &NtpServersSecondaryNtpServer{}
+ if o.NtpServers.SecondaryNtpServer.Misc != nil {
+ config.Misc["NtpServersSecondaryNtpServer"] = o.NtpServers.SecondaryNtpServer.Misc
+ }
+ if o.NtpServers.SecondaryNtpServer.NtpServerAddress != nil {
+ nestedNtpServers.SecondaryNtpServer.NtpServerAddress = o.NtpServers.SecondaryNtpServer.NtpServerAddress
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType = &NtpServersSecondaryNtpServerAuthenticationType{}
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.Misc != nil {
+ config.Misc["NtpServersSecondaryNtpServerAuthenticationType"] = o.NtpServers.SecondaryNtpServer.AuthenticationType.Misc
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.None != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.None = o.NtpServers.SecondaryNtpServer.AuthenticationType.None
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey{}
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Misc != nil {
+ config.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey"] = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Misc
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.KeyId
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5 != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5 = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5{}
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc != nil {
+ config.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5"] = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.Misc
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Md5.AuthenticationKey
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1 != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1 = &NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1{}
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc != nil {
+ config.Misc["NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1"] = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.Misc
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey = o.NtpServers.SecondaryNtpServer.AuthenticationType.SymmetricKey.Sha1.AuthenticationKey
+ }
+ }
+ }
+ if o.NtpServers.SecondaryNtpServer.AuthenticationType.Autokey != nil {
+ nestedNtpServers.SecondaryNtpServer.AuthenticationType.Autokey = o.NtpServers.SecondaryNtpServer.AuthenticationType.Autokey
+ }
+ }
+ }
+ }
+ config.NtpServers = nestedNtpServers
+
+ config.Misc["Config"] = o.Misc
+
+ configList = append(configList, config)
+ }
+
+ return configList, nil
+}
+
+func SpecMatches(a, b *Config) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchNtpServers(a.NtpServers, b.NtpServers) {
+ return false
+ }
+
+ return true
+}
+
+func matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1(a *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1, b *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.AuthenticationKey, b.AuthenticationKey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5(a *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5, b *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.AuthenticationKey, b.AuthenticationKey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey(a *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey, b *NtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.KeyId, b.KeyId) {
+ return false
+ }
+ if !matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeySha1(a.Sha1, b.Sha1) {
+ return false
+ }
+ if !matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKeyMd5(a.Md5, b.Md5) {
+ return false
+ }
+ return true
+}
+func matchNtpServersPrimaryNtpServerAuthenticationType(a *NtpServersPrimaryNtpServerAuthenticationType, b *NtpServersPrimaryNtpServerAuthenticationType) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.None, b.None) {
+ return false
+ }
+ if !matchNtpServersPrimaryNtpServerAuthenticationTypeSymmetricKey(a.SymmetricKey, b.SymmetricKey) {
+ return false
+ }
+ if !util.StringsMatch(a.Autokey, b.Autokey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersPrimaryNtpServer(a *NtpServersPrimaryNtpServer, b *NtpServersPrimaryNtpServer) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.NtpServerAddress, b.NtpServerAddress) {
+ return false
+ }
+ if !matchNtpServersPrimaryNtpServerAuthenticationType(a.AuthenticationType, b.AuthenticationType) {
+ return false
+ }
+ return true
+}
+func matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5(a *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5, b *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.AuthenticationKey, b.AuthenticationKey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1(a *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1, b *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.AuthenticationKey, b.AuthenticationKey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey(a *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey, b *NtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.KeyId, b.KeyId) {
+ return false
+ }
+ if !matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeyMd5(a.Md5, b.Md5) {
+ return false
+ }
+ if !matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKeySha1(a.Sha1, b.Sha1) {
+ return false
+ }
+ return true
+}
+func matchNtpServersSecondaryNtpServerAuthenticationType(a *NtpServersSecondaryNtpServerAuthenticationType, b *NtpServersSecondaryNtpServerAuthenticationType) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.None, b.None) {
+ return false
+ }
+ if !matchNtpServersSecondaryNtpServerAuthenticationTypeSymmetricKey(a.SymmetricKey, b.SymmetricKey) {
+ return false
+ }
+ if !util.StringsMatch(a.Autokey, b.Autokey) {
+ return false
+ }
+ return true
+}
+func matchNtpServersSecondaryNtpServer(a *NtpServersSecondaryNtpServer, b *NtpServersSecondaryNtpServer) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.NtpServerAddress, b.NtpServerAddress) {
+ return false
+ }
+ if !matchNtpServersSecondaryNtpServerAuthenticationType(a.AuthenticationType, b.AuthenticationType) {
+ return false
+ }
+ return true
+}
+func matchNtpServers(a *NtpServers, b *NtpServers) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchNtpServersPrimaryNtpServer(a.PrimaryNtpServer, b.PrimaryNtpServer) {
+ return false
+ }
+ if !matchNtpServersSecondaryNtpServer(a.SecondaryNtpServer, b.SecondaryNtpServer) {
+ return false
+ }
+ return true
+}
diff --git a/device/services/ntp/interfaces.go b/device/services/ntp/interfaces.go
new file mode 100644
index 0000000..a5e3956
--- /dev/null
+++ b/device/services/ntp/interfaces.go
@@ -0,0 +1,7 @@
+package ntp
+
+type Specifier func(*Config) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Config, error)
+}
diff --git a/device/services/ntp/location.go b/device/services/ntp/location.go
new file mode 100644
index 0000000..9dec490
--- /dev/null
+++ b/device/services/ntp/location.go
@@ -0,0 +1,180 @@
+package ntp
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ System *SystemLocation `json:"system,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type SystemLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewSystemLocation() *Location {
+ return &Location{System: &SystemLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.System != nil:
+ if o.System.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.System != nil:
+ if o.System.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.System.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ "deviceconfig",
+ "system",
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) Xpath(vn version.Number) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ return ans, nil
+}
diff --git a/device/services/ntp/service.go b/device/services/ntp/service.go
new file mode 100644
index 0000000..da4ab75
--- /dev/null
+++ b/device/services/ntp/service.go
@@ -0,0 +1,174 @@
+package ntp
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, config *Config) (*Config, error) {
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(config)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, action string) (*Config, error) {
+ return s.read(ctx, loc, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location) (*Config, error) {
+ return s.read(ctx, loc, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, action string, usePanosConfig bool) (*Config, error) {
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+func (s *Service) Update(ctx context.Context, loc Location, entry *Config) (*Config, error) {
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Config
+ old, err = s.Read(ctx, loc, "get")
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, config *Config) error {
+ return s.delete(ctx, loc, config)
+}
+func (s *Service) delete(ctx context.Context, loc Location, config *Config) error {
+
+ vn := s.client.Versioning()
+ path, err := loc.Xpath(vn)
+ if err != nil {
+ return err
+ }
+ deleteSuffixes := []string{}
+ deleteSuffixes = append(deleteSuffixes, "ntp-servers")
+
+ for _, suffix := range deleteSuffixes {
+ cmd := &xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(append(path, suffix)),
+ Target: s.client.GetTarget(),
+ }
+
+ _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/discard.go b/discard.go
new file mode 100644
index 0000000..8bad67d
--- /dev/null
+++ b/discard.go
@@ -0,0 +1,18 @@
+package pango
+
+import (
+ "context"
+ "log/slog"
+)
+
+// discardHandler is an slog handler which is always disabled.
+//
+// This slog handler implementation is always disabled, and therefore
+// logs nothing. It is used to filter out log categories not
+// explicitly enabled.
+type discardHandler struct{}
+
+func (d discardHandler) Enabled(context.Context, slog.Level) bool { return false }
+func (d discardHandler) Handle(context.Context, slog.Record) error { return nil }
+func (d discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return d }
+func (d discardHandler) WithGroup(name string) slog.Handler { return d }
diff --git a/errors/panos.go b/errors/panos.go
index b2e5b3c..16a604b 100644
--- a/errors/panos.go
+++ b/errors/panos.go
@@ -5,8 +5,6 @@ import (
stderr "errors"
"fmt"
"strings"
-
- "github.com/PaloAltoNetworks/pango/util"
)
var InvalidFilterError = stderr.New("filter is improperly formatted")
@@ -35,6 +33,15 @@ func (e Panos) ObjectNotFound() bool {
return e.Code == 7
}
+func IsObjectNotFound(e error) bool {
+ e2, ok := e.(Panos)
+ if ok && e2.ObjectNotFound() {
+ return true
+ }
+
+ return false
+}
+
// ObjectNotFound returns an object not found error.
func ObjectNotFound() Panos {
return Panos{
@@ -67,8 +74,12 @@ type errorCheck struct {
}
type errorCheckMsg struct {
- Line []util.CdataText `xml:"line"`
- Message string `xml:",chardata"`
+ Line []errLine `xml:"line"`
+ Message string `xml:",chardata"`
+}
+
+type errLine struct {
+ Text string `xml:",cdata"`
}
func (e *errorCheck) Failed() bool {
diff --git a/example/main.go b/example/main.go
new file mode 100644
index 0000000..7c04e4d
--- /dev/null
+++ b/example/main.go
@@ -0,0 +1,1099 @@
+package main
+
+import (
+ "context"
+ "encoding/xml"
+ "fmt"
+ "log"
+
+ "github.com/PaloAltoNetworks/pango"
+ "github.com/PaloAltoNetworks/pango/device/services/dns"
+ "github.com/PaloAltoNetworks/pango/device/services/ntp"
+ "github.com/PaloAltoNetworks/pango/network/interface/ethernet"
+ "github.com/PaloAltoNetworks/pango/network/interface/loopback"
+ "github.com/PaloAltoNetworks/pango/network/profiles/interface_management"
+ "github.com/PaloAltoNetworks/pango/network/virtual_router"
+ "github.com/PaloAltoNetworks/pango/network/zone"
+ "github.com/PaloAltoNetworks/pango/objects/address"
+ address_group "github.com/PaloAltoNetworks/pango/objects/address/group"
+ "github.com/PaloAltoNetworks/pango/objects/service"
+ service_group "github.com/PaloAltoNetworks/pango/objects/service/group"
+ "github.com/PaloAltoNetworks/pango/objects/tag"
+ "github.com/PaloAltoNetworks/pango/panorama/device_group"
+ "github.com/PaloAltoNetworks/pango/panorama/template"
+ "github.com/PaloAltoNetworks/pango/panorama/template_stack"
+ "github.com/PaloAltoNetworks/pango/policies/rules/security"
+ "github.com/PaloAltoNetworks/pango/rule"
+ "github.com/PaloAltoNetworks/pango/util"
+)
+
+func main() {
+ var err error
+ ctx := context.Background()
+
+ // FW
+ c := &pango.Client{
+ CheckEnvironment: true,
+ SkipVerifyCertificate: true,
+ ApiKeyInRequest: true,
+ }
+ if err = c.Setup(); err != nil {
+ log.Printf("Failed to setup client: %s", err)
+ return
+ }
+ log.Printf("Setup client %s (%s)", c.Hostname, c.Username)
+
+ if err = c.Initialize(ctx); err != nil {
+ log.Printf("Failed to initialize client: %s", err)
+ return
+ }
+
+ if ok, _ := c.IsPanorama(); ok {
+ log.Printf("Connected to Panorama, so templates and device groups are going to be created")
+ checkTemplate(c, ctx)
+ checkTemplateStack(c, ctx)
+ checkDeviceGroup(c, ctx)
+ } else {
+ log.Printf("Connected to firewall, so templates and device groups are not going to be created")
+ }
+
+ // CHECKS
+ checkVr(c, ctx)
+ checkZone(c, ctx)
+ checkInterfaceMgmtProfile(c, ctx)
+ checkEthernetLayer3Static(c, ctx)
+ checkEthernetLayer3Dhcp(c, ctx)
+ checkEthernetHa(c, ctx)
+ checkLoopback(c, ctx)
+ checkVrZoneWithEthernet(c, ctx)
+ checkSecurityPolicyRules(c, ctx)
+ checkSecurityPolicyRulesMove(c, ctx)
+ checkSharedObjects(c, ctx)
+ checkTag(c, ctx)
+ checkAddress(c, ctx)
+ checkService(c, ctx)
+ checkNtp(c, ctx)
+ checkDns(c, ctx)
+}
+
+func checkTemplate(c *pango.Client, ctx context.Context) {
+ entry := template.Entry{
+ Name: "codegen_template",
+ Description: util.String("This is a template created by codegen."),
+ DefaultVsys: util.String("vsys1"),
+ Config: &template.Config{
+ Devices: []template.ConfigDevices{
+ {
+ Name: "localhost.localdomain",
+ Vsys: []template.ConfigDevicesVsys{
+ {
+ Name: "vsys1",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ location := template.NewPanoramaLocation()
+ api := template.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create template: %s", err)
+ return
+ }
+ log.Printf("Template %s created\n", reply.Name)
+}
+
+func checkTemplateStack(c *pango.Client, ctx context.Context) {
+ entry := template_stack.Entry{
+ Name: "codegen_template_stack",
+ Description: util.String("This is a template stack created by codegen."),
+ Templates: []string{"codegen_template"},
+ DefaultVsys: util.String("vsys1"),
+ }
+
+ location := template_stack.NewPanoramaLocation()
+ api := template_stack.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create template stack: %s", err)
+ return
+ }
+ log.Printf("Template stack %s created\n", reply.Name)
+}
+
+func checkDeviceGroup(c *pango.Client, ctx context.Context) {
+ entry := device_group.Entry{
+ Name: "codegen_device_group",
+ Description: util.String("This is a device group created by codegen."),
+ Templates: []string{"codegen_template"},
+ }
+
+ location := device_group.NewPanoramaLocation()
+
+ api := device_group.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create device group: %s", err)
+ return
+ }
+ log.Printf("Device group %s created\n", reply.Name)
+}
+
+func checkSharedObjects(c *pango.Client, ctx context.Context) {
+ if ok, _ := c.IsPanorama(); ok {
+ addressObject := address.Entry{
+ Name: "codegen_address_shared1",
+ Description: util.String("This is a shared address created by codegen."),
+ IpNetmask: util.String("1.2.3.4"),
+ }
+
+ addressLocation := address.Location{
+ Shared: true,
+ }
+ addressApi := address.NewService(c)
+ addressReply, err := addressApi.Create(ctx, addressLocation, addressObject)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Address '%s=%s' created", addressReply.Name, *addressReply.IpNetmask)
+
+ err = addressApi.Delete(ctx, addressLocation, addressReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Address '%s' deleted", addressReply.Name)
+ }
+}
+
+func checkVr(c *pango.Client, ctx context.Context) {
+ entry := virtual_router.Entry{
+ Name: "codegen_vr",
+ Protocol: &virtual_router.Protocol{
+ Bgp: &virtual_router.ProtocolBgp{
+ Enable: util.Bool(false),
+ },
+ Ospf: &virtual_router.ProtocolOspf{
+ Enable: util.Bool(false),
+ },
+ Ospfv3: &virtual_router.ProtocolOspfv3{
+ Enable: util.Bool(false),
+ },
+ Rip: &virtual_router.ProtocolRip{
+ Enable: util.Bool(false),
+ },
+ },
+ RoutingTable: &virtual_router.RoutingTable{
+ // Ip: &virtual_router.RoutingTableIp{
+ // StaticRoutes: []virtual_router.RoutingTableIpStaticRoutes{
+ // {
+ // Name: "default",
+ // Destination: util.String("0.0.0.0/0"),
+ // Interface: util.String("ethernet1/2"),
+ // NextHop: &virtual_router.RoutingTableIpStaticRoutesNextHop{
+ // IpAddress: util.String("1.1.1.1"),
+ // },
+ // Metric: util.Int(64),
+ // AdminDist: util.Int(120),
+ // },
+ // },
+ // },
+ // Ipv6: &virtual_router.RoutingTableIpv6{
+ // StaticRoutes: []virtual_router.RoutingTableIpv6StaticRoutes{
+ // {
+ // Name: "default",
+ // Destination: util.String("0.0.0.0/0"),
+ // NextHop: &virtual_router.RoutingTableIpv6StaticRoutesNextHop{
+ // Ipv6Address: util.String("2001:0000:130F:0000:0000:09C0:876A:230D"),
+ // },
+ // Metric: util.Int(24),
+ // AdminDist: util.Int(20),
+ // },
+ // },
+ // },
+ },
+ Ecmp: &virtual_router.Ecmp{
+ Enable: util.Bool(true),
+ SymmetricReturn: util.Bool(true),
+ MaxPaths: util.Int(3),
+ Algorithm: &virtual_router.EcmpAlgorithm{
+ // IpHash: &virtual_router.EcmpAlgorithmIpHash{
+ // HashSeed: util.Int(1234),
+ // UsePort: util.Bool(true),
+ // SrcOnly: util.Bool(true),
+ // },
+ // WeightedRoundRobin: &virtual_router.EcmpAlgorithmWeightedRoundRobin{
+ // Interfaces: []virtual_router.EcmpAlgorithmWeightedRoundRobinInterfaces{
+ // {
+ // Name: "ethernet1/2",
+ // Weight: util.Int(1),
+ // },
+ // {
+ // Name: "ethernet1/3",
+ // Weight: util.Int(2),
+ // },
+ // },
+ // },
+ },
+ },
+ AdministrativeDistances: &virtual_router.AdministrativeDistances{
+ OspfInt: util.Int(77),
+ OspfExt: util.Int(88),
+ },
+ }
+ var location *virtual_router.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = virtual_router.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = virtual_router.NewNgfwLocation()
+ }
+ api := virtual_router.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create VR: %s", err)
+ return
+ }
+ log.Printf("VR %s created\n", reply.Name)
+}
+
+func checkEthernetLayer3Static(c *pango.Client, ctx context.Context) {
+ entry := ethernet.Entry{
+ Name: "ethernet1/2",
+ Comment: util.String("This is a ethernet1/2"),
+ Layer3: ðernet.Layer3{
+ NdpProxy: util.Bool(true),
+ Lldp: util.Bool(true),
+ AdjustTcpMss: ðernet.Layer3AdjustTcpMss{
+ Enable: util.Bool(true),
+ Ipv4MssAdjustment: util.Int(250),
+ Ipv6MssAdjustment: util.Int(250),
+ },
+ Mtu: util.Int(1280),
+ Ips: []string{"11.11.11.11", "22.22.22.22"},
+ Ipv6: ðernet.Layer3Ipv6{
+ Addresses: []ethernet.Layer3Ipv6Addresses{
+ {
+ EnableOnInterface: util.Bool(false),
+ Name: "2001:0000:130F:0000:0000:09C0:876A:230B",
+ },
+ {
+ EnableOnInterface: util.Bool(true),
+ Name: "2001:0000:130F:0000:0000:09C0:876A:230C",
+ },
+ },
+ },
+ InterfaceManagementProfile: util.String("codegen_mgmt_profile"),
+ },
+ }
+ var location *ethernet.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = ethernet.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = ethernet.NewNgfwLocation()
+ }
+ api := ethernet.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create ethernet: %s", err)
+ return
+ }
+ log.Printf("Ethernet layer3 %s created\n", reply.Name)
+}
+
+func checkEthernetLayer3Dhcp(c *pango.Client, ctx context.Context) {
+ entry := ethernet.Entry{
+ Name: "ethernet1/3",
+ Comment: util.String("This is a ethernet1/3"),
+ Layer3: ðernet.Layer3{
+ InterfaceManagementProfile: util.String("codegen_mgmt_profile"),
+ DhcpClient: ðernet.Layer3DhcpClient{
+ CreateDefaultRoute: util.Bool(false),
+ DefaultRouteMetric: util.Int(64),
+ Enable: util.Bool(true),
+ SendHostname: ðernet.Layer3DhcpClientSendHostname{
+ Enable: util.Bool(true),
+ Hostname: util.String("codegen_dhcp"),
+ },
+ },
+ },
+ }
+ var location *ethernet.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = ethernet.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = ethernet.NewNgfwLocation()
+ }
+ api := ethernet.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create ethernet: %s", err)
+ return
+ }
+ log.Printf("Ethernet layer3 %s created\n", reply.Name)
+}
+
+func checkEthernetHa(c *pango.Client, ctx context.Context) {
+ entry := ethernet.Entry{
+ Name: "ethernet1/10",
+ Comment: util.String("This is a ethernet1/10"),
+ Ha: ðernet.Ha{},
+ }
+ var location *ethernet.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = ethernet.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = ethernet.NewNgfwLocation()
+ }
+ api := ethernet.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create ethernet: %s", err)
+ return
+ }
+ log.Printf("Ethernet HA %s created\n", reply.Name)
+}
+
+func checkLoopback(c *pango.Client, ctx context.Context) {
+ entry := loopback.Entry{
+ Name: "loopback.123",
+ AdjustTcpMss: &loopback.AdjustTcpMss{
+ Enable: util.Bool(true),
+ Ipv4MssAdjustment: util.Int(250),
+ Ipv6MssAdjustment: util.Int(250),
+ },
+ Comment: util.String("This is a loopback entry"),
+ Mtu: util.Int(1280),
+ Ips: []string{"1.1.1.1", "2.2.2.2"},
+ Ipv6: &loopback.Ipv6{
+ Addresses: []loopback.Ipv6Addresses{
+ {
+ EnableOnInterface: util.Bool(false),
+ Name: "2001:0000:130F:0000:0000:09C0:876A:130B",
+ },
+ {
+ EnableOnInterface: util.Bool(true),
+ Name: "2001:0000:130F:0000:0000:09C0:876A:130C",
+ },
+ },
+ },
+ InterfaceManagementProfile: util.String("codegen_mgmt_profile"),
+ }
+ var location *loopback.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = loopback.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = loopback.NewNgfwLocation()
+ }
+ api := loopback.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create loopback: %s", err)
+ return
+ }
+ log.Printf("Loopback %s created\n", reply.Name)
+}
+
+func checkZone(c *pango.Client, ctx context.Context) {
+ entry := zone.Entry{
+ Name: "codegen_zone",
+ EnableUserIdentification: util.Bool(true),
+ Network: &zone.Network{
+ EnablePacketBufferProtection: util.Bool(false),
+ Layer3: []string{},
+ },
+ DeviceAcl: &zone.DeviceAcl{
+ IncludeList: []string{"1.2.3.4"},
+ },
+ UserAcl: &zone.UserAcl{
+ ExcludeList: []string{"1.2.3.4"},
+ },
+ }
+ var location *zone.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = zone.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = zone.NewVsysLocation()
+ }
+ api := zone.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create zone: %s", err)
+ return
+ }
+ log.Printf("Zone %s created\n", reply.Name)
+}
+
+func checkInterfaceMgmtProfile(c *pango.Client, ctx context.Context) {
+ entry := interface_management.Entry{
+ Name: "codegen_mgmt_profile",
+ Http: util.Bool(true),
+ Ping: util.Bool(true),
+ PermittedIps: []string{"1.1.1.1", "2.2.2.2"},
+ }
+ var location *interface_management.Location
+ if ok, _ := c.IsPanorama(); ok {
+ location = interface_management.NewTemplateLocation()
+ location.Template.Template = "codegen_template"
+ } else {
+ location = interface_management.NewNgfwLocation()
+ }
+ api := interface_management.NewService(c)
+
+ reply, err := api.Create(ctx, *location, entry)
+ if err != nil {
+ log.Printf("Failed to create interface management profile: %s", err)
+ return
+ }
+ log.Printf("Interface management profile %s created\n", reply.Name)
+}
+
+func checkVrZoneWithEthernet(c *pango.Client, ctx context.Context) {
+ // UPDATE VR ABOUT INTERFACES
+ var locationVr *virtual_router.Location
+ if ok, _ := c.IsPanorama(); ok {
+ locationVr = virtual_router.NewTemplateLocation()
+ locationVr.Template.Template = "codegen_template"
+ } else {
+ locationVr = virtual_router.NewNgfwLocation()
+ }
+ apiVr := virtual_router.NewService(c)
+
+ replyVr, err := apiVr.Read(ctx, *locationVr, "codegen_vr", "get")
+ if err != nil {
+ log.Printf("Failed to read VR: %s", err)
+ return
+ }
+ log.Printf("VR %s read\n", replyVr.Name)
+
+ replyVr.Interfaces = []string{"ethernet1/2", "ethernet1/3"}
+
+ replyVr, err = apiVr.Update(ctx, *locationVr, *replyVr, "codegen_vr")
+ if err != nil {
+ log.Printf("Failed to update VR: %s", err)
+ return
+ }
+ log.Printf("VR %s updated with %s\n", replyVr.Name, replyVr.Interfaces)
+
+ // UPDATE ZONE ABOUT INTERFACES
+ var locationZone *zone.Location
+ if ok, _ := c.IsPanorama(); ok {
+ locationZone = zone.NewTemplateLocation()
+ locationZone.Template.Template = "codegen_template"
+ } else {
+ locationZone = zone.NewVsysLocation()
+ }
+ apiZone := zone.NewService(c)
+
+ replyZone, err := apiZone.Read(ctx, *locationZone, "codegen_zone", "get")
+ if err != nil {
+ log.Printf("Failed to read zone: %s", err)
+ return
+ }
+ log.Printf("Zone %s read\n", replyZone.Name)
+
+ replyZone.Network = &zone.Network{
+ EnablePacketBufferProtection: util.Bool(false),
+ Layer3: []string{"ethernet1/2", "ethernet1/3"},
+ }
+
+ replyZone, err = apiZone.Update(ctx, *locationZone, *replyZone, "codegen_zone")
+ if err != nil {
+ log.Printf("Failed to update zone: %s", err)
+ return
+ }
+ log.Printf("Zone %s updated with %s\n", replyZone.Name, replyZone.Network.Layer3)
+
+ // DELETE INTERFACES FROM VR
+ replyVr.Interfaces = []string{}
+
+ replyVr, err = apiVr.Update(ctx, *locationVr, *replyVr, "codegen_vr")
+ if err != nil {
+ log.Printf("Failed to update VR: %s", err)
+ return
+ }
+ log.Printf("VR %s updated with %s\n", replyVr.Name, replyVr.Interfaces)
+
+ // DELETE INTERFACES FROM ZONE
+ replyZone.Network = &zone.Network{
+ EnablePacketBufferProtection: util.Bool(false),
+ Layer3: []string{},
+ }
+
+ replyZone, err = apiZone.Update(ctx, *locationZone, *replyZone, "codegen_zone")
+ if err != nil {
+ log.Printf("Failed to update zone: %s", err)
+ return
+ }
+ log.Printf("Zone %s updated with %s\n", replyZone.Name, replyZone.Network.Layer3)
+
+ // DELETE INTERFACES
+ var ethernetLocation *ethernet.Location
+ if ok, _ := c.IsPanorama(); ok {
+ ethernetLocation = ethernet.NewTemplateLocation()
+ ethernetLocation.Template.Template = "codegen_template"
+ } else {
+ ethernetLocation = ethernet.NewNgfwLocation()
+ }
+ api := ethernet.NewService(c)
+
+ interfacesToDelete := []string{"ethernet1/2", "ethernet1/3"}
+ for _, iface := range interfacesToDelete {
+ err = api.Delete(ctx, *ethernetLocation, iface)
+ if err != nil {
+ log.Printf("Failed to delete ethernet: %s", err)
+ return
+ }
+ log.Printf("Ethernet %s deleted\n", iface)
+ }
+}
+
+func checkSecurityPolicyRules(c *pango.Client, ctx context.Context) {
+ // SECURITY POLICY RULE - ADD
+ securityPolicyRuleEntry := security.Entry{
+ Name: "codegen_rule",
+ Description: util.String("initial description"),
+ Action: util.String("allow"),
+ SourceZones: []string{"any"},
+ SourceAddresses: []string{"any"},
+ DestinationZones: []string{"any"},
+ DestinationAddresses: []string{"any"},
+ Applications: []string{"any"},
+ Services: []string{"application-default"},
+ }
+
+ var securityPolicyRuleLocation *security.Location
+ if ok, _ := c.IsPanorama(); ok {
+ securityPolicyRuleLocation = security.NewDeviceGroupLocation()
+ securityPolicyRuleLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ securityPolicyRuleLocation = security.NewVsysLocation()
+ }
+
+ securityPolicyRuleApi := security.NewService(c)
+ securityPolicyRuleReply, err := securityPolicyRuleApi.Create(ctx, *securityPolicyRuleLocation, securityPolicyRuleEntry)
+ if err != nil {
+ log.Printf("Failed to create security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' created", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, *securityPolicyRuleReply.Description)
+
+ // SECURITY POLICY RULE - READ
+ securityPolicyRuleReply, err = securityPolicyRuleApi.Read(ctx, *securityPolicyRuleLocation, securityPolicyRuleReply.Name, "get")
+ if err != nil {
+ log.Printf("Failed to update security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' read", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, *securityPolicyRuleReply.Description)
+
+ // SECURITY POLICY RULE - UPDATE
+ securityPolicyRuleEntry.Description = util.String("changed description")
+ securityPolicyRuleReply, err = securityPolicyRuleApi.Update(ctx, *securityPolicyRuleLocation, securityPolicyRuleEntry, securityPolicyRuleReply.Name)
+ if err != nil {
+ log.Printf("Failed to update security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' updated", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, *securityPolicyRuleReply.Description)
+
+ // SECURITY POLICY RULE - READ BY ID
+ securityPolicyRuleReply, err = securityPolicyRuleApi.ReadById(ctx, *securityPolicyRuleLocation, *securityPolicyRuleReply.Uuid, "get")
+ if err != nil {
+ log.Printf("Failed to update security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' read by id", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, *securityPolicyRuleReply.Description)
+
+ // SECURITY POLICY RULE - UPDATE 2
+ securityPolicyRuleEntry.Description = util.String("changed by id description")
+ securityPolicyRuleReply, err = securityPolicyRuleApi.UpdateById(ctx, *securityPolicyRuleLocation, securityPolicyRuleEntry, *securityPolicyRuleReply.Uuid)
+ if err != nil {
+ log.Printf("Failed to update security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' updated", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, *securityPolicyRuleReply.Description)
+
+ // SECURITY POLICY RULE - HIT COUNT
+ if ok, _ := c.IsFirewall(); ok {
+ hitCount, err := securityPolicyRuleApi.HitCount(ctx, *securityPolicyRuleLocation, "test-policy")
+ if err != nil {
+ log.Printf("Failed to get hit count for security policy rule: %s", err)
+ return
+ }
+ if len(hitCount) > 0 {
+ log.Printf("Security policy rule '%d' hit count", hitCount[0].HitCount)
+ } else {
+ log.Printf("Security policy rule not found")
+ }
+ }
+
+ // SECURITY POLICY RULE - AUDIT COMMENT
+ err = securityPolicyRuleApi.SetAuditComment(ctx, *securityPolicyRuleLocation, securityPolicyRuleReply.Name, "another audit comment")
+ if err != nil {
+ log.Printf("Failed to set audit comment for security policy rule: %s", err)
+ return
+ }
+
+ comment, err := securityPolicyRuleApi.CurrentAuditComment(ctx, *securityPolicyRuleLocation, securityPolicyRuleEntry.Name)
+ if err != nil {
+ log.Printf("Failed to get audit comment for security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' current comment: '%s'", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, comment)
+
+ comments, err := securityPolicyRuleApi.AuditCommentHistory(ctx, *securityPolicyRuleLocation, securityPolicyRuleEntry.Name, "forward", 10, 0)
+ if err != nil {
+ log.Printf("Failed to get audit comments for security policy rule: %s", err)
+ return
+ }
+ for _, comment := range comments {
+ log.Printf("Security policy rule '%s:%s' comment history: '%s:%s'", *securityPolicyRuleReply.Uuid, securityPolicyRuleReply.Name, comment.Time, comment.Comment)
+ }
+
+ // SECURITY POLICY RULE - DELETE
+ err = securityPolicyRuleApi.DeleteById(ctx, *securityPolicyRuleLocation, *securityPolicyRuleReply.Uuid)
+ if err != nil {
+ log.Printf("Failed to delete security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s' deleted", securityPolicyRuleReply.Name)
+
+ // SECURITY POLICY RULE - FORCE ERROR WHILE DELETE
+ err = securityPolicyRuleApi.Delete(ctx, *securityPolicyRuleLocation, securityPolicyRuleReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete security policy rule: %s", err)
+ } else {
+ log.Printf("Security policy rule '%s' deleted", securityPolicyRuleReply.Name)
+ }
+}
+
+func checkSecurityPolicyRulesMove(c *pango.Client, ctx context.Context) {
+ // SECURITY POLICY RULE - MOVE GROUP
+ var securityPolicyRuleLocation *security.Location
+ if ok, _ := c.IsPanorama(); ok {
+ securityPolicyRuleLocation = security.NewDeviceGroupLocation()
+ securityPolicyRuleLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ securityPolicyRuleLocation = security.NewVsysLocation()
+ }
+
+ securityPolicyRuleApi := security.NewService(c)
+
+ securityPolicyRulesNames := make([]string, 10)
+ var securityPolicyRulesEntries []security.Entry
+ for i := 0; i < 10; i++ {
+ securityPolicyRulesNames[i] = fmt.Sprintf("codegen_rule%d", i)
+ securityPolicyRuleItem := security.Entry{
+ Name: securityPolicyRulesNames[i],
+ Description: util.String("initial description"),
+ Action: util.String("allow"),
+ SourceZones: []string{"any"},
+ SourceAddresses: []string{"any"},
+ DestinationZones: []string{"any"},
+ DestinationAddresses: []string{"any"},
+ Applications: []string{"any"},
+ Services: []string{"application-default"},
+ }
+ securityPolicyRulesEntries = append(securityPolicyRulesEntries, securityPolicyRuleItem)
+ securityPolicyRuleItemReply, err := securityPolicyRuleApi.Create(ctx, *securityPolicyRuleLocation, securityPolicyRuleItem)
+ if err != nil {
+ log.Printf("Failed to create security policy rule: %s", err)
+ return
+ }
+ log.Printf("Security policy rule '%s:%s' with description '%s' created", *securityPolicyRuleItemReply.Uuid, securityPolicyRuleItemReply.Name, *securityPolicyRuleItemReply.Description)
+ }
+ rulePositionBefore7 := rule.Position{
+ First: nil,
+ Last: nil,
+ SomewhereBefore: nil,
+ DirectlyBefore: util.String("codegen_rule7"),
+ SomewhereAfter: nil,
+ DirectlyAfter: nil,
+ }
+ rulePositionBottom := rule.Position{
+ First: nil,
+ Last: util.Bool(true),
+ SomewhereBefore: nil,
+ DirectlyBefore: nil,
+ SomewhereAfter: nil,
+ DirectlyAfter: nil,
+ }
+ var securityPolicyRulesEntriesToMove []security.Entry
+ securityPolicyRulesEntriesToMove = append(securityPolicyRulesEntriesToMove, securityPolicyRulesEntries[3])
+ securityPolicyRulesEntriesToMove = append(securityPolicyRulesEntriesToMove, securityPolicyRulesEntries[5])
+ for _, securityPolicyRuleItemToMove := range securityPolicyRulesEntriesToMove {
+ log.Printf("Security policy rule '%s' is going to be moved", securityPolicyRuleItemToMove.Name)
+ }
+ err := securityPolicyRuleApi.MoveGroup(ctx, *securityPolicyRuleLocation, rulePositionBefore7, securityPolicyRulesEntriesToMove)
+ if err != nil {
+ log.Printf("Failed to move security policy rules %v: %s", securityPolicyRulesEntriesToMove, err)
+ return
+ }
+ securityPolicyRulesEntriesToMove = []security.Entry{securityPolicyRulesEntries[1]}
+ for _, securityPolicyRuleItemToMove := range securityPolicyRulesEntriesToMove {
+ log.Printf("Security policy rule '%s' is going to be moved", securityPolicyRuleItemToMove.Name)
+ }
+ err = securityPolicyRuleApi.MoveGroup(ctx, *securityPolicyRuleLocation, rulePositionBottom, securityPolicyRulesEntriesToMove)
+ if err != nil {
+ log.Printf("Failed to move security policy rules %v: %s", securityPolicyRulesEntriesToMove, err)
+ return
+ }
+ err = securityPolicyRuleApi.Delete(ctx, *securityPolicyRuleLocation, securityPolicyRulesNames...)
+ if err != nil {
+ log.Printf("Failed to delete security policy rules %s: %s", securityPolicyRulesNames, err)
+ return
+ }
+}
+
+func checkTag(c *pango.Client, ctx context.Context) {
+ // TAG - CREATE
+ tagColor := tag.ColorAzureBlue
+ tagObject := tag.Entry{
+ Name: "codegen_color",
+ Color: &tagColor,
+ }
+
+ var tagLocation *tag.Location
+ if ok, _ := c.IsPanorama(); ok {
+ tagLocation = tag.NewDeviceGroupLocation()
+ tagLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ tagLocation = tag.NewSharedLocation()
+ }
+
+ tagApi := tag.NewService(c)
+ tagReply, err := tagApi.Create(ctx, *tagLocation, tagObject)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Tag '%s' created", tagReply.Name)
+
+ // TAG - DELETE
+ err = tagApi.Delete(ctx, *tagLocation, tagReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Tag '%s' deleted", tagReply.Name)
+}
+
+func checkAddress(c *pango.Client, ctx context.Context) {
+ // ADDRESS - CREATE
+ addressObject := address.Entry{
+ Name: "codegen_address_test1",
+ IpNetmask: util.String("12.13.14.25"),
+ }
+
+ var addressLocation *address.Location
+ if ok, _ := c.IsPanorama(); ok {
+ addressLocation = address.NewDeviceGroupLocation()
+ addressLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ addressLocation = address.NewSharedLocation()
+ }
+
+ addressApi := address.NewService(c)
+ addressReply, err := addressApi.Create(ctx, *addressLocation, addressObject)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Address '%s=%s' created", addressReply.Name, *addressReply.IpNetmask)
+
+ // ADDRESS - LIST
+ addresses, err := addressApi.List(ctx, *addressLocation, "get", "name starts-with 'codegen'", "'")
+ if err != nil {
+ log.Printf("Failed to list object: %s", err)
+ } else {
+ for index, item := range addresses {
+ log.Printf("Address %d: '%s'", index, item.Name)
+ }
+ }
+
+ // ADDRESS - GROUP
+ addressGroupObject := address_group.Entry{
+ Name: "codegen_address_group_test1",
+ Static: []string{addressReply.Name},
+ }
+
+ var addressGroupLocation *address_group.Location
+ if ok, _ := c.IsPanorama(); ok {
+ addressGroupLocation = address_group.NewDeviceGroupLocation()
+ addressGroupLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ addressGroupLocation = address_group.NewSharedLocation()
+ }
+
+ addressGroupApi := address_group.NewService(c)
+ addressGroupReply, err := addressGroupApi.Create(ctx, *addressGroupLocation, addressGroupObject)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Address group '%s' created", addressGroupReply.Name)
+
+ // ADDRESS - GROUP - DELETE
+ err = addressGroupApi.Delete(ctx, *addressGroupLocation, addressGroupReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Address group '%s' deleted", addressGroupReply.Name)
+
+ // ADDRESS - DELETE
+ err = addressApi.Delete(ctx, *addressLocation, addressReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Address '%s' deleted", addressReply.Name)
+}
+
+func checkService(c *pango.Client, ctx context.Context) {
+ // SERVICE - ADD
+ serviceObject := service.Entry{
+ Name: "codegen_service_test1",
+ Description: util.String("test description"),
+ Protocol: &service.Protocol{
+ Tcp: &service.ProtocolTcp{
+ DestinationPort: util.Int(8642),
+ Override: &service.ProtocolTcpOverride{
+ HalfcloseTimeout: util.Int(124),
+ Timeout: util.Int(125),
+ TimewaitTimeout: util.Int(127),
+ },
+ },
+ },
+ }
+
+ var serviceLocation *service.Location
+ if ok, _ := c.IsPanorama(); ok {
+ serviceLocation = service.NewDeviceGroupLocation()
+ serviceLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ serviceLocation = service.NewVsysLocation()
+ }
+
+ serviceApi := service.NewService(c)
+ serviceReply, err := serviceApi.Create(ctx, *serviceLocation, serviceObject)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Service '%s=%d' created", serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort)
+
+ // SERVICE - UPDATE 1
+ serviceObject.Description = util.String("changed description")
+
+ serviceReply, err = serviceApi.Update(ctx, *serviceLocation, serviceObject, serviceReply.Name)
+ if err != nil {
+ log.Printf("Failed to update object: %s", err)
+ return
+ }
+ log.Printf("Service '%s=%d' updated", serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort)
+
+ // SERVICE - UPDATE 2
+ serviceObject.Protocol.Tcp.DestinationPort = util.Int(1234)
+
+ serviceReply, err = serviceApi.Update(ctx, *serviceLocation, serviceObject, serviceReply.Name)
+ if err != nil {
+ log.Printf("Failed to update object: %s", err)
+ return
+ }
+ log.Printf("Service '%s=%d' updated", serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort)
+
+ // SERVICE - RENAME
+ newServiceName := "codegen_service_test2"
+ serviceObject.Name = newServiceName
+
+ serviceReply, err = serviceApi.Update(ctx, *serviceLocation, serviceObject, serviceReply.Name)
+ if err != nil {
+ log.Printf("Failed to update object: %s", err)
+ return
+ }
+ log.Printf("Service '%s=%d' renamed", serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort)
+
+ // SERVICE GROUP ADD
+ serviceGroupEntry := service_group.Entry{
+ Name: "codegen_service_group_test1",
+ Members: []string{serviceReply.Name},
+ }
+
+ var serviceGroupLocation *service_group.Location
+ if ok, _ := c.IsPanorama(); ok {
+ serviceGroupLocation = service_group.NewDeviceGroupLocation()
+ serviceGroupLocation.DeviceGroup.DeviceGroup = "codegen_device_group"
+ } else {
+ serviceGroupLocation = service_group.NewVsysLocation()
+ }
+
+ serviceGroupApi := service_group.NewService(c)
+ serviceGroupReply, err := serviceGroupApi.Create(ctx, *serviceGroupLocation, serviceGroupEntry)
+ if err != nil {
+ log.Printf("Failed to create object: %s", err)
+ return
+ }
+ log.Printf("Service group '%s' created", serviceGroupReply.Name)
+
+ // SERVICE GROUP DELETE
+ err = serviceGroupApi.Delete(ctx, *serviceGroupLocation, serviceGroupReply.Name)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Service group '%s' deleted", serviceGroupReply.Name)
+
+ // SERVICE - LIST
+ //services, err := serviceApi.List(ctx, serviceLocation, "get", "name starts-with 'test'", "'")
+ services, err := serviceApi.List(ctx, *serviceLocation, "get", "", "")
+ if err != nil {
+ log.Printf("Failed to list object: %s", err)
+ } else {
+ for index, item := range services {
+ log.Printf("Service %d: '%s'", index, item.Name)
+ }
+ }
+
+ // SERVICE - DELETE
+ err = serviceApi.Delete(ctx, *serviceLocation, newServiceName)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Printf("Service '%s' deleted", newServiceName)
+
+ // SERVICE - READ
+ serviceLocation = service.NewVsysLocation()
+
+ serviceApi = service.NewService(c)
+ serviceReply, err = serviceApi.Read(ctx, *serviceLocation, "test", "get")
+ if err != nil {
+ log.Printf("Failed to read object: %s", err)
+ return
+ }
+ readDescription := ""
+ if serviceReply.Description != nil {
+ readDescription = *serviceReply.Description
+ }
+ keys := make([]string, 0, len(serviceReply.Misc))
+ xmls := make([]string, 0, len(serviceReply.Misc))
+ for key := range serviceReply.Misc {
+ keys = append(keys, key)
+ data, _ := xml.Marshal(serviceReply.Misc[key])
+ xmls = append(xmls, string(data))
+ }
+ log.Printf("Service '%s=%d, description: %s misc XML: %s, misc keys: %s' read",
+ serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort, readDescription, xmls, keys)
+
+ // SERVICE - UPDATE 3
+ serviceReply.Description = util.String("some text changed now")
+
+ serviceReply, err = serviceApi.Update(ctx, *serviceLocation, *serviceReply, "test")
+ if err != nil {
+ log.Printf("Failed to update object: %s", err)
+ return
+ }
+ readDescription = ""
+ if serviceReply.Description != nil {
+ readDescription = *serviceReply.Description
+ }
+ keys = make([]string, 0, len(serviceReply.Misc))
+ xmls = make([]string, 0, len(serviceReply.Misc))
+ for key := range serviceReply.Misc {
+ keys = append(keys, key)
+ data, _ := xml.Marshal(serviceReply.Misc[key])
+ xmls = append(xmls, string(data))
+ }
+ log.Printf("Service '%s=%d, description: %s misc XML: %s, misc keys: %s' update",
+ serviceReply.Name, *serviceReply.Protocol.Tcp.DestinationPort, readDescription, xmls, keys)
+}
+
+func checkNtp(c *pango.Client, ctx context.Context) {
+ // NTP - ADD
+ ntpConfig := ntp.Config{
+ NtpServers: &ntp.NtpServers{
+ PrimaryNtpServer: &ntp.NtpServersPrimaryNtpServer{
+ NtpServerAddress: util.String("11.12.13.14"),
+ },
+ },
+ }
+
+ var ntpLocation *ntp.Location
+ if ok, _ := c.IsPanorama(); ok {
+ ntpLocation = ntp.NewTemplateLocation()
+ ntpLocation.Template.Template = "codegen_template"
+ } else {
+ ntpLocation = ntp.NewSystemLocation()
+ }
+
+ ntpApi := ntp.NewService(c)
+ ntpReply, err := ntpApi.Create(ctx, *ntpLocation, ntpConfig)
+ if err != nil {
+ log.Printf("Failed to create NTP: %s", err)
+ return
+ }
+ log.Printf("NTP '%s' created", *ntpReply.NtpServers.PrimaryNtpServer.NtpServerAddress)
+
+ // NTP - DELETE
+ err = ntpApi.Delete(ctx, *ntpLocation, ntpConfig)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Print("NTP deleted")
+ return
+}
+
+func checkDns(c *pango.Client, ctx context.Context) {
+ // DNS - ADD
+ dnsConfig := dns.Config{
+ DnsSetting: &dns.DnsSetting{
+ Servers: &dns.DnsSettingServers{
+ Primary: util.String("8.8.8.8"),
+ Secondary: util.String("4.4.4.4"),
+ },
+ },
+ FqdnRefreshTime: util.Int(27),
+ }
+
+ var dnsLocation *dns.Location
+ if ok, _ := c.IsPanorama(); ok {
+ dnsLocation = dns.NewTemplateLocation()
+ dnsLocation.Template.Template = "codegen_template"
+ } else {
+ dnsLocation = dns.NewSystemLocation()
+ }
+
+ dnsApi := dns.NewService(c)
+ dnsReply, err := dnsApi.Create(ctx, *dnsLocation, dnsConfig)
+ if err != nil {
+ log.Printf("Failed to create DNS: %s", err)
+ return
+ }
+ log.Printf("DNS '%s, %s' created", *dnsReply.DnsSetting.Servers.Primary, *dnsReply.DnsSetting.Servers.Secondary)
+
+ // DNS - DELETE
+ err = dnsApi.Delete(ctx, *dnsLocation, dnsConfig)
+ if err != nil {
+ log.Printf("Failed to delete object: %s", err)
+ return
+ }
+ log.Print("DNS deleted")
+}
diff --git a/filtering/parse.go b/filtering/parse.go
index 45b5ce3..514ccbc 100644
--- a/filtering/parse.go
+++ b/filtering/parse.go
@@ -9,12 +9,12 @@ import (
)
func Parse(s string, quote string) (*Group, error) {
- if len(quote) != 1 {
+ if s == "" {
+ return nil, nil
+ } else if len(quote) != 1 {
return nil, fmt.Errorf("quote character should be one character")
} else if quote == "&" || quote == "|" || quote == "(" || quote == ")" || quote == " " || quote == `\` || quote == "!" || quote == "." || quote == "<" || quote == ">" || quote == "=" || quote == "-" || quote == "_" {
return nil, fmt.Errorf("quote character cannot be a reserved character")
- } else if s == "" {
- return nil, nil
}
var ch rune
diff --git a/filtering/parse_test.go b/filtering/parse_test.go
index 411b5db..0e38864 100644
--- a/filtering/parse_test.go
+++ b/filtering/parse_test.go
@@ -932,6 +932,7 @@ func TestParseReturnsError(t *testing.T) {
"foobar",
"(",
")",
+ "",
}
for _, s := range checks {
@@ -965,12 +966,3 @@ func TestParseWithInvalidQuoteReturnsError(t *testing.T) {
}
}
}
-
-func TestParseEmptyString(t *testing.T) {
- resp, err := Parse("", `"`)
- if err != nil {
- t.Fatalf("parse empty string error: %s", err)
- } else if resp != nil {
- t.Fatalf("parse empty string has non-nil group")
- }
-}
diff --git a/go.mod b/go.mod
index 3866f35..8d42ce0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,20 @@
module github.com/PaloAltoNetworks/pango
-go 1.21
+go 1.22.5
+
+require (
+ github.com/onsi/ginkgo/v2 v2.19.0
+ github.com/onsi/gomega v1.33.1
+)
+
+require (
+ github.com/go-logr/logr v1.4.1 // indirect
+ github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ golang.org/x/tools v0.21.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..1564c9b
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,23 @@
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
+github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
+github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
+github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
+github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
+golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/logging.go b/logging.go
new file mode 100644
index 0000000..c3e71ee
--- /dev/null
+++ b/logging.go
@@ -0,0 +1,196 @@
+package pango
+
+import (
+ "fmt"
+ "log/slog"
+ "math/bits"
+)
+
+// LogCategory is a bitmask describing what categories of logging to enable.
+//
+// Available bit-wise flags are as follows:
+//
+// - LogCategoryPango: Basic library-level logging
+// - LogCategoryOp: Logging of operation commands (Op functions)
+// - LogCategorySend: Logging of the data being sent to the server. All sensitive
+// data will be scrubbed from the response unless LogCategorySensitive
+// is explicitly added to the mask
+// - LogCategoryReceive: Logging of the data being received from the server. All
+// sensitive data will be scrubbed from the response unless LogCategorySensitive
+// is explicitly added to the mask
+// - LogCategoryCurl: When used along with LogCategorySend, an equivalent curl
+// command will be logged
+// - LogCategoryAll: A meta-category, enabling all above categories at once
+// - LogCategorySensitive: Logging of sensitive data like hostnames, logins,
+// passwords or API keys of any sort
+type LogCategory uint
+
+const (
+ LogCategoryPango LogCategory = 1 << iota
+ LogCategoryOp
+ LogCategorySend
+ LogCategoryReceive
+ LogCategoryCurl
+ LogCategoryAll = LogCategoryPango | LogCategoryOp | LogCategorySend |
+ LogCategoryReceive | LogCategoryCurl
+ // Make sure that LogCategorySensitive is always last, explicitly set to 1 << 32
+ LogCategorySensitive LogCategory = 1 << 32
+)
+
+var logCategoryToString = map[LogCategory]string{
+ LogCategoryPango: "pango",
+ LogCategoryOp: "op",
+ LogCategorySend: "send",
+ LogCategoryReceive: "receive",
+ LogCategoryCurl: "curl",
+ LogCategoryAll: "all",
+ LogCategorySensitive: "sensitive",
+}
+
+func createStringToCategoryMap(categories map[LogCategory]string) map[string]LogCategory {
+ // Instead of keeping two maps for two way association, we
+ // just generate reversed map on the fly. This function is not
+ // going to be used outside of the initial library setup, so
+ // the slight performance penalty is not an issue.
+ stringsMap := make(map[string]LogCategory, len(logCategoryToString))
+ for category, sym := range logCategoryToString {
+ stringsMap[sym] = category
+ }
+
+ return stringsMap
+}
+
+// LogCategoryFromStrings transforms list with categories into its bitmask equivalent.
+//
+// This function takes a list of strings, representing log categories
+// that can be used to filter what gets logged by pango library. This list
+// can change over time as more categories are added to the library.
+//
+// It returns LogCategory bitmask which can be then used to configure
+// logger. If unknown log category string is given as part of the
+// list, error is returned instead.
+func LogCategoryFromStrings(symbols []string) (LogCategory, error) {
+ stringsMap := createStringToCategoryMap(logCategoryToString)
+
+ var logCategoriesMask LogCategory
+ for _, elt := range symbols {
+ category, ok := stringsMap[elt]
+ if !ok {
+ return 0, fmt.Errorf("unknown log category: %s", elt)
+ }
+
+ logCategoriesMask |= category
+ slog.Info("logCategoriesMask", "equal", logCategoriesMask)
+ }
+ return logCategoriesMask, nil
+}
+
+// LogCategoryAsStrings interprets given LogCategory bitmask into its string representation.
+//
+// This function takes LogCategory bitmask as argument, and converts
+// it into a list of strings, where each element represents a single
+// category. LogCategoryAll is converted into a list of enabled
+// categories, without "all".
+//
+// It returns a list of categories as strings, or error if invalid
+// LogCategory mask has been provided.
+func LogCategoryAsStrings(categories LogCategory) ([]string, error) {
+ symbols := make([]string, 0)
+
+ // Calculate a number of high bits in the categories mask, to make
+ // sure all categories other than LogCategoryAll have been matched.
+ highBits := bits.OnesCount(uint(categories))
+
+ // Iterate over all available log categories, skipping
+ // LogCategoryAll as we can't distinguish between explicitly
+ // ORing all LogCategories and using LogCategoryAll.
+ for key, value := range logCategoryToString {
+ if key == LogCategoryAll {
+ continue
+ }
+ if categories&key == key {
+ symbols = append(symbols, value)
+ }
+ }
+
+ // Return an error if number of high bits in the categories
+ // mask is lower than length of the symbols list
+ if len(symbols) < highBits && (categories&LogCategoryAll != LogCategoryAll) {
+ return nil, fmt.Errorf("invalid LogCategory bitmask")
+ }
+
+ return symbols, nil
+}
+
+// LogCategoryToSymbol returns string representation of the given LogCategory
+//
+// The given LogCategory can only have single bit set high, and cannot
+// match LogCategoryAll. To convert LogCategory bitmask into a list of categories,
+// use LogCategoryToStrings instead.
+//
+// It returns string representation of the log category, or error if
+// unknown category has been provided.
+func LogCategoryToString(category LogCategory) (string, error) {
+ if category&LogCategoryAll == LogCategoryAll {
+ return "", fmt.Errorf("cannot convert LogCategoryAll into a category string.")
+ }
+ symbol, ok := logCategoryToString[category]
+ if ok {
+ return symbol, nil
+ }
+
+ return "", fmt.Errorf("unknown LogCategory: %d", category)
+}
+
+// StringToLogCategory returns LogCategory mask matching given string category.
+//
+// The given string should be a single category, and not "all". To convert "all"
+// into a list of enabled log categories, use LogCategoryFromStrings.
+//
+// It returns LogCategory representation of the given category string, or en
+// error if either "all" or unknown string has been given.
+func StringToLogCategory(sym string) (LogCategory, error) {
+ if sym == logCategoryToString[LogCategoryAll] {
+ return 0, fmt.Errorf("cannot convert \"all\" category string into LogCategory")
+ }
+ for key, value := range logCategoryToString {
+ if value == sym {
+ return key, nil
+ }
+ }
+
+ return 0, fmt.Errorf("Unknown logging symbol: %s", sym)
+}
+
+type categoryLogger struct {
+ logger *slog.Logger
+ discardLogger *slog.Logger
+ categories LogCategory
+}
+
+func newCategoryLogger(logger *slog.Logger, categories LogCategory) *categoryLogger {
+ return &categoryLogger{
+ logger: logger,
+ discardLogger: slog.New(discardHandler{}),
+ categories: categories,
+ }
+}
+
+func (l *categoryLogger) WithLogCategory(category LogCategory) *slog.Logger {
+ matched, ok := logCategoryToString[category]
+
+ // If the category cannot be matched, instead of returning
+ // error we use "unknown" instead.
+ if !ok {
+ matched = "unknown"
+ }
+
+ if l.categories&category == category {
+ return l.logger.WithGroup(matched)
+ }
+ return l.discardLogger.WithGroup(matched)
+}
+
+func (l *categoryLogger) enabledFor(category LogCategory) bool {
+ return l.categories&category == category
+}
diff --git a/logging_test.go b/logging_test.go
new file mode 100644
index 0000000..b48a73e
--- /dev/null
+++ b/logging_test.go
@@ -0,0 +1,90 @@
+package pango
+
+import (
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("While configuring logging", func() {
+ When("converting category strings into LogCategory bitmask", func() {
+ Context("with unknown string category", func() {
+ It("should fail with error message", func() {
+ categories := []string{"invalid"}
+ _, err := LogCategoryFromStrings(categories)
+ Expect(err).Should(HaveOccurred())
+ })
+ })
+
+ Context("with \"all\" string present", func() {
+ It("should return LogCategoryMask without LogCategorySensitive bit set", func() {
+ categories := []string{"all"}
+ categoriesMask, err := LogCategoryFromStrings(categories)
+ Expect(err).Should(Succeed())
+ Expect(categoriesMask & LogCategorySensitive).ShouldNot(Equal(LogCategorySensitive))
+ })
+ })
+
+ Context("with \"sensitive\" string present", func() {
+ It("should return LogCategoryMask with LogCategorySensitive bit set", func() {
+ categories := []string{"receive", "sensitive"}
+ categoriesMask, err := LogCategoryFromStrings(categories)
+ Expect(err).Should(Succeed())
+ expectedMask := LogCategoryReceive | LogCategorySensitive
+ Expect(categoriesMask).To(Equal(expectedMask))
+ })
+ })
+ })
+
+ When("converting LogCategory bitmask into category strings", func() {
+ Context("with invalid bitmask set", func() {
+ It("should return error about LogCategoty bitmask being invalid", func() {
+ categories := LogCategory(1 << 31)
+ _, err := LogCategoryAsStrings(categories)
+ Expect(err).Should(HaveOccurred())
+ })
+ })
+
+ Context("with valid bitmask without LogCategorySensitive", func() {
+ It("the list should not \"sensitive\" category", func() {
+ categories := LogCategoryReceive | LogCategorySend
+ result, err := LogCategoryAsStrings(categories)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).Should(ContainElements([]string{"send", "receive"}))
+ Expect(result).ShouldNot(ContainElement("sensitive"))
+ })
+ })
+
+ Context("with bitmask set to LogCategoryAll", func() {
+ var categories []string
+ BeforeEach(func() {
+ for _, v := range logCategoryToString {
+ if v != "all" && v != "sensitive" {
+ categories = append(categories, v)
+ }
+
+ }
+ })
+
+ It("the list should not contain \"all\" category", func() {
+ result, err := LogCategoryAsStrings(LogCategoryAll)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).ShouldNot(ContainElement("all"))
+ })
+
+ It("the list should not contain \"sensitive\" category", func() {
+ result, err := LogCategoryAsStrings(LogCategoryAll)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).ShouldNot(ContainElement("sensitive"))
+ })
+ })
+
+ Context("with explicitly added LogCategorySensitive", func() {
+ It("should have \"sensitive\" element", func() {
+ result, err := LogCategoryAsStrings(LogCategoryCurl | LogCategorySensitive)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).To(HaveLen(2))
+ Expect(result).Should(ContainElements([]string{"curl", "sensitive"}))
+ })
+ })
+ })
+})
diff --git a/network/interface/ethernet/entry.go b/network/interface/ethernet/entry.go
new file mode 100644
index 0000000..483db0c
--- /dev/null
+++ b/network/interface/ethernet/entry.go
@@ -0,0 +1,1823 @@
+package ethernet
+
+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{"network", "interface", "ethernet"}
+)
+
+type Entry struct {
+ Name string
+ Comment *string
+ LinkDuplex *string
+ LinkSpeed *string
+ LinkState *string
+ Poe *Poe
+ Ha *Ha
+ Layer3 *Layer3
+ Tap *Tap
+
+ Misc map[string][]generic.Xml
+}
+
+type Ha struct {
+}
+type Layer3 struct {
+ AdjustTcpMss *Layer3AdjustTcpMss
+ Arp []Layer3Arp
+ Bonjour *Layer3Bonjour
+ DhcpClient *Layer3DhcpClient
+ InterfaceManagementProfile *string
+ Ips []Layer3Ips
+ Ipv6 *Layer3Ipv6
+ Lldp *Layer3Lldp
+ Mtu *int64
+ NdpProxy *bool
+ NetflowProfile *string
+ SdwanLinkSettings *Layer3SdwanLinkSettings
+ UntaggedSubInterface *bool
+}
+type Layer3AdjustTcpMss struct {
+ Enable *bool
+ Ipv4MssAdjustment *int64
+ Ipv6MssAdjustment *int64
+}
+type Layer3Arp struct {
+ HwAddress *string
+ Name string
+}
+type Layer3Bonjour struct {
+ Enable *bool
+}
+type Layer3DhcpClient struct {
+ CreateDefaultRoute *bool
+ DefaultRouteMetric *int64
+ Enable *bool
+ SendHostname *Layer3DhcpClientSendHostname
+}
+type Layer3DhcpClientSendHostname struct {
+ Enable *bool
+ Hostname *string
+}
+type Layer3Ips struct {
+ Name string
+ SdwanGateway *string
+}
+type Layer3Ipv6 struct {
+ Addresses []Layer3Ipv6Addresses
+ DnsServer *Layer3Ipv6DnsServer
+ Enabled *bool
+ InterfaceId *string
+ NeighborDiscovery *Layer3Ipv6NeighborDiscovery
+}
+type Layer3Ipv6Addresses struct {
+ Advertise *Layer3Ipv6AddressesAdvertise
+ Anycast *string
+ EnableOnInterface *bool
+ Name string
+ Prefix *string
+}
+type Layer3Ipv6AddressesAdvertise struct {
+ AutoConfigFlag *bool
+ Enable *bool
+ OnlinkFlag *bool
+ PreferredLifetime *string
+ ValidLifetime *string
+}
+type Layer3Ipv6DnsServer struct {
+ DnsSupport *Layer3Ipv6DnsServerDnsSupport
+ Enable *bool
+ Source *Layer3Ipv6DnsServerSource
+}
+type Layer3Ipv6DnsServerDnsSupport struct {
+ Enable *bool
+ Server []Layer3Ipv6DnsServerDnsSupportServer
+ Suffix []Layer3Ipv6DnsServerDnsSupportSuffix
+}
+type Layer3Ipv6DnsServerDnsSupportServer struct {
+ Lifetime *int64
+ Name string
+}
+type Layer3Ipv6DnsServerDnsSupportSuffix struct {
+ Lifetime *int64
+ Name string
+}
+type Layer3Ipv6DnsServerSource struct {
+ Dhcpv6 *Layer3Ipv6DnsServerSourceDhcpv6
+ Manual *Layer3Ipv6DnsServerSourceManual
+}
+type Layer3Ipv6DnsServerSourceDhcpv6 struct {
+ PrefixPool *string
+}
+type Layer3Ipv6DnsServerSourceManual struct {
+ Suffix []Layer3Ipv6DnsServerSourceManualSuffix
+}
+type Layer3Ipv6DnsServerSourceManualSuffix struct {
+ Lifetime *int64
+ Name string
+}
+type Layer3Ipv6NeighborDiscovery struct {
+ DadAttempts *int64
+ EnableDad *bool
+ EnableNdpMonitor *bool
+ Neighbor []Layer3Ipv6NeighborDiscoveryNeighbor
+ NsInterval *int64
+ ReachableTime *int64
+ RouterAdvertisement *Layer3Ipv6NeighborDiscoveryRouterAdvertisement
+}
+type Layer3Ipv6NeighborDiscoveryNeighbor struct {
+ HwAddress *string
+ Name string
+}
+type Layer3Ipv6NeighborDiscoveryRouterAdvertisement struct {
+ Enable *bool
+ EnableConsistencyCheck *bool
+ HopLimit *string
+ Lifetime *int64
+ LinkMtu *string
+ ManagedFlag *bool
+ MaxInterval *int64
+ MinInterval *int64
+ OtherFlag *bool
+ ReachableTime *string
+ RetransmissionTimer *string
+ RouterPreference *string
+}
+type Layer3Lldp struct {
+ Enable *bool
+ Profile *string
+}
+type Layer3SdwanLinkSettings struct {
+ Enable *bool
+ SdwanInterfaceProfile *string
+ UpstreamNat *Layer3SdwanLinkSettingsUpstreamNat
+}
+type Layer3SdwanLinkSettingsUpstreamNat struct {
+ Enable *bool
+ StaticIp *string
+}
+type Poe struct {
+ Enabled *bool
+ ReservedPower *int64
+}
+type Tap struct {
+ NetflowProfile *string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Comment *string `xml:"comment,omitempty"`
+ LinkDuplex *string `xml:"link-duplex,omitempty"`
+ LinkSpeed *string `xml:"link-speed,omitempty"`
+ LinkState *string `xml:"link-state,omitempty"`
+ Poe *PoeXml
+ Ha *HaXml `xml:"ha,omitempty"`
+ Layer3 *Layer3Xml `xml:"layer3,omitempty"`
+ Tap *TapXml `xml:"tap,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type HaXml struct {
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Xml struct {
+ AdjustTcpMss *Layer3AdjustTcpMssXml `xml:"adjust-tcp-mss,omitempty"`
+ Arp []Layer3ArpXml `xml:"arp>entry,omitempty"`
+ Bonjour *Layer3BonjourXml `xml:"bonjour,omitempty"`
+ DhcpClient *Layer3DhcpClientXml `xml:"dhcp-client,omitempty"`
+ InterfaceManagementProfile *string `xml:"interface-management-profile,omitempty"`
+ Ips []Layer3IpsXml `xml:"ip>entry,omitempty"`
+ Ipv6 *Layer3Ipv6Xml `xml:"ipv6,omitempty"`
+ Lldp *Layer3LldpXml `xml:"lldp,omitempty"`
+ Mtu *int64 `xml:"mtu,omitempty"`
+ NdpProxy *string `xml:"ndp-proxy>enabled,omitempty"`
+ NetflowProfile *string `xml:"netflow-profile,omitempty"`
+ SdwanLinkSettings *Layer3SdwanLinkSettingsXml `xml:"sdwan-link-settings,omitempty"`
+ UntaggedSubInterface *string `xml:"untagged-sub-interface,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3AdjustTcpMssXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ Ipv4MssAdjustment *int64 `xml:"ipv4-mss-adjustment,omitempty"`
+ Ipv6MssAdjustment *int64 `xml:"ipv6-mss-adjustment,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3ArpXml struct {
+ HwAddress *string `xml:"hw-address,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3BonjourXml struct {
+ Enable *string
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3DhcpClientXml struct {
+ CreateDefaultRoute *string `xml:"create-default-route,omitempty"`
+ DefaultRouteMetric *int64 `xml:"default-route-metric,omitempty"`
+ Enable *string `xml:"enable,omitempty"`
+ SendHostname *Layer3DhcpClientSendHostnameXml `xml:"send-hostname,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3DhcpClientSendHostnameXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ Hostname *string `xml:"hostname,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3IpsXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ SdwanGateway *string `xml:"sdwan-gateway,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6Xml struct {
+ Addresses []Layer3Ipv6AddressesXml `xml:"address>entry,omitempty"`
+ DnsServer *Layer3Ipv6DnsServerXml `xml:"dns-server,omitempty"`
+ Enabled *string `xml:"enabled,omitempty"`
+ InterfaceId *string `xml:"interface-id,omitempty"`
+ NeighborDiscovery *Layer3Ipv6NeighborDiscoveryXml `xml:"neighbor-discovery,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6AddressesXml struct {
+ Advertise *Layer3Ipv6AddressesAdvertiseXml `xml:"advertise,omitempty"`
+ Anycast *string `xml:"anycast,omitempty"`
+ EnableOnInterface *string `xml:"enable-on-interface,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Prefix *string `xml:"prefix,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6AddressesAdvertiseXml struct {
+ AutoConfigFlag *string `xml:"auto-config-flag,omitempty"`
+ Enable *string `xml:"enable,omitempty"`
+ OnlinkFlag *string `xml:"onlink-flag,omitempty"`
+ PreferredLifetime *string `xml:"preferred-lifetime,omitempty"`
+ ValidLifetime *string `xml:"valid-lifetime,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerXml struct {
+ DnsSupport *Layer3Ipv6DnsServerDnsSupportXml `xml:"dns-support,omitempty"`
+ Enable *string `xml:"enable,omitempty"`
+ Source *Layer3Ipv6DnsServerSourceXml `xml:"source,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerDnsSupportXml struct {
+ Enable *string
+ Server []Layer3Ipv6DnsServerDnsSupportServerXml `xml:"server>entry,omitempty"`
+ Suffix []Layer3Ipv6DnsServerDnsSupportSuffixXml `xml:"suffix>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerDnsSupportServerXml struct {
+ Lifetime *int64
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerDnsSupportSuffixXml struct {
+ Lifetime *int64
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerSourceXml struct {
+ Dhcpv6 *Layer3Ipv6DnsServerSourceDhcpv6Xml `xml:"dhcpv6,omitempty"`
+ Manual *Layer3Ipv6DnsServerSourceManualXml `xml:"manual,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerSourceDhcpv6Xml struct {
+ PrefixPool *string `xml:"prefix-pool,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerSourceManualXml struct {
+ Suffix []Layer3Ipv6DnsServerSourceManualSuffixXml `xml:"suffix>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6DnsServerSourceManualSuffixXml struct {
+ Lifetime *int64 `xml:"lifetime,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6NeighborDiscoveryXml struct {
+ DadAttempts *int64 `xml:"dad-attempts,omitempty"`
+ EnableDad *string `xml:"enable-dad,omitempty"`
+ EnableNdpMonitor *string `xml:"enable-ndp-monitor,omitempty"`
+ Neighbor []Layer3Ipv6NeighborDiscoveryNeighborXml `xml:"neighbor>entry,omitempty"`
+ NsInterval *int64 `xml:"ns-interval,omitempty"`
+ ReachableTime *int64 `xml:"reachable-time,omitempty"`
+ RouterAdvertisement *Layer3Ipv6NeighborDiscoveryRouterAdvertisementXml `xml:"router-advertisement,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6NeighborDiscoveryNeighborXml struct {
+ HwAddress *string `xml:"hw-address,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3Ipv6NeighborDiscoveryRouterAdvertisementXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ EnableConsistencyCheck *string `xml:"enable-consistency-check,omitempty"`
+ HopLimit *string `xml:"hop-limit,omitempty"`
+ Lifetime *int64 `xml:"lifetime,omitempty"`
+ LinkMtu *string `xml:"link-mtu,omitempty"`
+ ManagedFlag *string `xml:"managed-flag,omitempty"`
+ MaxInterval *int64 `xml:"max-interval,omitempty"`
+ MinInterval *int64 `xml:"min-interval,omitempty"`
+ OtherFlag *string `xml:"other-flag,omitempty"`
+ ReachableTime *string `xml:"reachable-time,omitempty"`
+ RetransmissionTimer *string `xml:"retransmission-timer,omitempty"`
+ RouterPreference *string `xml:"router-preference,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3LldpXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ Profile *string `xml:"profile,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3SdwanLinkSettingsXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ SdwanInterfaceProfile *string `xml:"sdwan-interface-profile,omitempty"`
+ UpstreamNat *Layer3SdwanLinkSettingsUpstreamNatXml `xml:"upstream-nat,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Layer3SdwanLinkSettingsUpstreamNatXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ StaticIp *string `xml:"static-ip>ip-address,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type PoeXml struct {
+ Enabled *string `xml:"poe-enabled,omitempty"`
+ ReservedPower *int64 `xml:"poe-rsvd-pwr,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type TapXml struct {
+ NetflowProfile *string `xml:"netflow-profile,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "comment" || v == "Comment" {
+ return e.Comment, nil
+ }
+ if v == "link_duplex" || v == "LinkDuplex" {
+ return e.LinkDuplex, nil
+ }
+ if v == "link_speed" || v == "LinkSpeed" {
+ return e.LinkSpeed, nil
+ }
+ if v == "link_state" || v == "LinkState" {
+ return e.LinkState, nil
+ }
+ if v == "poe" || v == "Poe" {
+ return e.Poe, nil
+ }
+ if v == "ha" || v == "Ha" {
+ return e.Ha, nil
+ }
+ if v == "layer3" || v == "Layer3" {
+ return e.Layer3, nil
+ }
+ if v == "tap" || v == "Tap" {
+ return e.Tap, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Comment = o.Comment
+ entry.LinkDuplex = o.LinkDuplex
+ entry.LinkSpeed = o.LinkSpeed
+ entry.LinkState = o.LinkState
+ var nestedPoe *PoeXml
+ if o.Poe != nil {
+ nestedPoe = &PoeXml{}
+ if _, ok := o.Misc["Poe"]; ok {
+ nestedPoe.Misc = o.Misc["Poe"]
+ }
+ if o.Poe.ReservedPower != nil {
+ nestedPoe.ReservedPower = o.Poe.ReservedPower
+ }
+ if o.Poe.Enabled != nil {
+ nestedPoe.Enabled = util.YesNo(o.Poe.Enabled, nil)
+ }
+ }
+ entry.Poe = nestedPoe
+
+ var nestedHa *HaXml
+ if o.Ha != nil {
+ nestedHa = &HaXml{}
+ if _, ok := o.Misc["Ha"]; ok {
+ nestedHa.Misc = o.Misc["Ha"]
+ }
+ }
+ entry.Ha = nestedHa
+
+ var nestedLayer3 *Layer3Xml
+ if o.Layer3 != nil {
+ nestedLayer3 = &Layer3Xml{}
+ if _, ok := o.Misc["Layer3"]; ok {
+ nestedLayer3.Misc = o.Misc["Layer3"]
+ }
+ if o.Layer3.Ips != nil {
+ nestedLayer3.Ips = []Layer3IpsXml{}
+ for _, oLayer3Ips := range o.Layer3.Ips {
+ nestedLayer3Ips := Layer3IpsXml{}
+ if _, ok := o.Misc["Layer3Ips"]; ok {
+ nestedLayer3Ips.Misc = o.Misc["Layer3Ips"]
+ }
+ if oLayer3Ips.SdwanGateway != nil {
+ nestedLayer3Ips.SdwanGateway = oLayer3Ips.SdwanGateway
+ }
+ if oLayer3Ips.Name != "" {
+ nestedLayer3Ips.Name = oLayer3Ips.Name
+ }
+ nestedLayer3.Ips = append(nestedLayer3.Ips, nestedLayer3Ips)
+ }
+ }
+ if o.Layer3.Ipv6 != nil {
+ nestedLayer3.Ipv6 = &Layer3Ipv6Xml{}
+ if _, ok := o.Misc["Layer3Ipv6"]; ok {
+ nestedLayer3.Ipv6.Misc = o.Misc["Layer3Ipv6"]
+ }
+ if o.Layer3.Ipv6.Addresses != nil {
+ nestedLayer3.Ipv6.Addresses = []Layer3Ipv6AddressesXml{}
+ for _, oLayer3Ipv6Addresses := range o.Layer3.Ipv6.Addresses {
+ nestedLayer3Ipv6Addresses := Layer3Ipv6AddressesXml{}
+ if _, ok := o.Misc["Layer3Ipv6Addresses"]; ok {
+ nestedLayer3Ipv6Addresses.Misc = o.Misc["Layer3Ipv6Addresses"]
+ }
+ if oLayer3Ipv6Addresses.Anycast != nil {
+ nestedLayer3Ipv6Addresses.Anycast = oLayer3Ipv6Addresses.Anycast
+ }
+ if oLayer3Ipv6Addresses.Advertise != nil {
+ nestedLayer3Ipv6Addresses.Advertise = &Layer3Ipv6AddressesAdvertiseXml{}
+ if _, ok := o.Misc["Layer3Ipv6AddressesAdvertise"]; ok {
+ nestedLayer3Ipv6Addresses.Advertise.Misc = o.Misc["Layer3Ipv6AddressesAdvertise"]
+ }
+ if oLayer3Ipv6Addresses.Advertise.Enable != nil {
+ nestedLayer3Ipv6Addresses.Advertise.Enable = util.YesNo(oLayer3Ipv6Addresses.Advertise.Enable, nil)
+ }
+ if oLayer3Ipv6Addresses.Advertise.ValidLifetime != nil {
+ nestedLayer3Ipv6Addresses.Advertise.ValidLifetime = oLayer3Ipv6Addresses.Advertise.ValidLifetime
+ }
+ if oLayer3Ipv6Addresses.Advertise.PreferredLifetime != nil {
+ nestedLayer3Ipv6Addresses.Advertise.PreferredLifetime = oLayer3Ipv6Addresses.Advertise.PreferredLifetime
+ }
+ if oLayer3Ipv6Addresses.Advertise.OnlinkFlag != nil {
+ nestedLayer3Ipv6Addresses.Advertise.OnlinkFlag = util.YesNo(oLayer3Ipv6Addresses.Advertise.OnlinkFlag, nil)
+ }
+ if oLayer3Ipv6Addresses.Advertise.AutoConfigFlag != nil {
+ nestedLayer3Ipv6Addresses.Advertise.AutoConfigFlag = util.YesNo(oLayer3Ipv6Addresses.Advertise.AutoConfigFlag, nil)
+ }
+ }
+ if oLayer3Ipv6Addresses.Name != "" {
+ nestedLayer3Ipv6Addresses.Name = oLayer3Ipv6Addresses.Name
+ }
+ if oLayer3Ipv6Addresses.EnableOnInterface != nil {
+ nestedLayer3Ipv6Addresses.EnableOnInterface = util.YesNo(oLayer3Ipv6Addresses.EnableOnInterface, nil)
+ }
+ if oLayer3Ipv6Addresses.Prefix != nil {
+ nestedLayer3Ipv6Addresses.Prefix = oLayer3Ipv6Addresses.Prefix
+ }
+ nestedLayer3.Ipv6.Addresses = append(nestedLayer3.Ipv6.Addresses, nestedLayer3Ipv6Addresses)
+ }
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery = &Layer3Ipv6NeighborDiscoveryXml{}
+ if _, ok := o.Misc["Layer3Ipv6NeighborDiscovery"]; ok {
+ nestedLayer3.Ipv6.NeighborDiscovery.Misc = o.Misc["Layer3Ipv6NeighborDiscovery"]
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.Neighbor != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.Neighbor = []Layer3Ipv6NeighborDiscoveryNeighborXml{}
+ for _, oLayer3Ipv6NeighborDiscoveryNeighbor := range o.Layer3.Ipv6.NeighborDiscovery.Neighbor {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor := Layer3Ipv6NeighborDiscoveryNeighborXml{}
+ if _, ok := o.Misc["Layer3Ipv6NeighborDiscoveryNeighbor"]; ok {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor.Misc = o.Misc["Layer3Ipv6NeighborDiscoveryNeighbor"]
+ }
+ if oLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress != nil {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress = oLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress
+ }
+ if oLayer3Ipv6NeighborDiscoveryNeighbor.Name != "" {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor.Name = oLayer3Ipv6NeighborDiscoveryNeighbor.Name
+ }
+ nestedLayer3.Ipv6.NeighborDiscovery.Neighbor = append(nestedLayer3.Ipv6.NeighborDiscovery.Neighbor, nestedLayer3Ipv6NeighborDiscoveryNeighbor)
+ }
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.EnableNdpMonitor != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.EnableNdpMonitor = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.EnableNdpMonitor, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.EnableDad != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.EnableDad = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.EnableDad, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.DadAttempts != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.DadAttempts = o.Layer3.Ipv6.NeighborDiscovery.DadAttempts
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.NsInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.NsInterval = o.Layer3.Ipv6.NeighborDiscovery.NsInterval
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.ReachableTime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.ReachableTime = o.Layer3.Ipv6.NeighborDiscovery.ReachableTime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement = &Layer3Ipv6NeighborDiscoveryRouterAdvertisementXml{}
+ if _, ok := o.Misc["Layer3Ipv6NeighborDiscoveryRouterAdvertisement"]; ok {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Misc = o.Misc["Layer3Ipv6NeighborDiscoveryRouterAdvertisement"]
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable = util.YesNo(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable, nil)
+ }
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer != nil {
+ nestedLayer3.Ipv6.DnsServer = &Layer3Ipv6DnsServerXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServer"]; ok {
+ nestedLayer3.Ipv6.DnsServer.Misc = o.Misc["Layer3Ipv6DnsServer"]
+ }
+ if o.Layer3.Ipv6.DnsServer.Enable != nil {
+ nestedLayer3.Ipv6.DnsServer.Enable = util.YesNo(o.Layer3.Ipv6.DnsServer.Enable, nil)
+ }
+ if o.Layer3.Ipv6.DnsServer.Source != nil {
+ nestedLayer3.Ipv6.DnsServer.Source = &Layer3Ipv6DnsServerSourceXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerSource"]; ok {
+ nestedLayer3.Ipv6.DnsServer.Source.Misc = o.Misc["Layer3Ipv6DnsServerSource"]
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Dhcpv6 != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Dhcpv6 = &Layer3Ipv6DnsServerSourceDhcpv6Xml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerSourceDhcpv6"]; ok {
+ nestedLayer3.Ipv6.DnsServer.Source.Dhcpv6.Misc = o.Misc["Layer3Ipv6DnsServerSourceDhcpv6"]
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool = o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Manual != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Manual = &Layer3Ipv6DnsServerSourceManualXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerSourceManual"]; ok {
+ nestedLayer3.Ipv6.DnsServer.Source.Manual.Misc = o.Misc["Layer3Ipv6DnsServerSourceManual"]
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Manual.Suffix != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix = []Layer3Ipv6DnsServerSourceManualSuffixXml{}
+ for _, oLayer3Ipv6DnsServerSourceManualSuffix := range o.Layer3.Ipv6.DnsServer.Source.Manual.Suffix {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix := Layer3Ipv6DnsServerSourceManualSuffixXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerSourceManualSuffix"]; ok {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix.Misc = o.Misc["Layer3Ipv6DnsServerSourceManualSuffix"]
+ }
+ if oLayer3Ipv6DnsServerSourceManualSuffix.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix.Lifetime = oLayer3Ipv6DnsServerSourceManualSuffix.Lifetime
+ }
+ if oLayer3Ipv6DnsServerSourceManualSuffix.Name != "" {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix.Name = oLayer3Ipv6DnsServerSourceManualSuffix.Name
+ }
+ nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix = append(nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix, nestedLayer3Ipv6DnsServerSourceManualSuffix)
+ }
+ }
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport = &Layer3Ipv6DnsServerDnsSupportXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerDnsSupport"]; ok {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Misc = o.Misc["Layer3Ipv6DnsServerDnsSupport"]
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Enable != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Enable = util.YesNo(o.Layer3.Ipv6.DnsServer.DnsSupport.Enable, nil)
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Server != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Server = []Layer3Ipv6DnsServerDnsSupportServerXml{}
+ for _, oLayer3Ipv6DnsServerDnsSupportServer := range o.Layer3.Ipv6.DnsServer.DnsSupport.Server {
+ nestedLayer3Ipv6DnsServerDnsSupportServer := Layer3Ipv6DnsServerDnsSupportServerXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerDnsSupportServer"]; ok {
+ nestedLayer3Ipv6DnsServerDnsSupportServer.Misc = o.Misc["Layer3Ipv6DnsServerDnsSupportServer"]
+ }
+ if oLayer3Ipv6DnsServerDnsSupportServer.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerDnsSupportServer.Lifetime = oLayer3Ipv6DnsServerDnsSupportServer.Lifetime
+ }
+ if oLayer3Ipv6DnsServerDnsSupportServer.Name != "" {
+ nestedLayer3Ipv6DnsServerDnsSupportServer.Name = oLayer3Ipv6DnsServerDnsSupportServer.Name
+ }
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Server = append(nestedLayer3.Ipv6.DnsServer.DnsSupport.Server, nestedLayer3Ipv6DnsServerDnsSupportServer)
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Suffix != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix = []Layer3Ipv6DnsServerDnsSupportSuffixXml{}
+ for _, oLayer3Ipv6DnsServerDnsSupportSuffix := range o.Layer3.Ipv6.DnsServer.DnsSupport.Suffix {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix := Layer3Ipv6DnsServerDnsSupportSuffixXml{}
+ if _, ok := o.Misc["Layer3Ipv6DnsServerDnsSupportSuffix"]; ok {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix.Misc = o.Misc["Layer3Ipv6DnsServerDnsSupportSuffix"]
+ }
+ if oLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime = oLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime
+ }
+ if oLayer3Ipv6DnsServerDnsSupportSuffix.Name != "" {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix.Name = oLayer3Ipv6DnsServerDnsSupportSuffix.Name
+ }
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix = append(nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix, nestedLayer3Ipv6DnsServerDnsSupportSuffix)
+ }
+ }
+ }
+ }
+ if o.Layer3.Ipv6.Enabled != nil {
+ nestedLayer3.Ipv6.Enabled = util.YesNo(o.Layer3.Ipv6.Enabled, nil)
+ }
+ if o.Layer3.Ipv6.InterfaceId != nil {
+ nestedLayer3.Ipv6.InterfaceId = o.Layer3.Ipv6.InterfaceId
+ }
+ }
+ if o.Layer3.AdjustTcpMss != nil {
+ nestedLayer3.AdjustTcpMss = &Layer3AdjustTcpMssXml{}
+ if _, ok := o.Misc["Layer3AdjustTcpMss"]; ok {
+ nestedLayer3.AdjustTcpMss.Misc = o.Misc["Layer3AdjustTcpMss"]
+ }
+ if o.Layer3.AdjustTcpMss.Ipv4MssAdjustment != nil {
+ nestedLayer3.AdjustTcpMss.Ipv4MssAdjustment = o.Layer3.AdjustTcpMss.Ipv4MssAdjustment
+ }
+ if o.Layer3.AdjustTcpMss.Ipv6MssAdjustment != nil {
+ nestedLayer3.AdjustTcpMss.Ipv6MssAdjustment = o.Layer3.AdjustTcpMss.Ipv6MssAdjustment
+ }
+ if o.Layer3.AdjustTcpMss.Enable != nil {
+ nestedLayer3.AdjustTcpMss.Enable = util.YesNo(o.Layer3.AdjustTcpMss.Enable, nil)
+ }
+ }
+ if o.Layer3.Arp != nil {
+ nestedLayer3.Arp = []Layer3ArpXml{}
+ for _, oLayer3Arp := range o.Layer3.Arp {
+ nestedLayer3Arp := Layer3ArpXml{}
+ if _, ok := o.Misc["Layer3Arp"]; ok {
+ nestedLayer3Arp.Misc = o.Misc["Layer3Arp"]
+ }
+ if oLayer3Arp.HwAddress != nil {
+ nestedLayer3Arp.HwAddress = oLayer3Arp.HwAddress
+ }
+ if oLayer3Arp.Name != "" {
+ nestedLayer3Arp.Name = oLayer3Arp.Name
+ }
+ nestedLayer3.Arp = append(nestedLayer3.Arp, nestedLayer3Arp)
+ }
+ }
+ if o.Layer3.NdpProxy != nil {
+ nestedLayer3.NdpProxy = util.YesNo(o.Layer3.NdpProxy, nil)
+ }
+ if o.Layer3.Lldp != nil {
+ nestedLayer3.Lldp = &Layer3LldpXml{}
+ if _, ok := o.Misc["Layer3Lldp"]; ok {
+ nestedLayer3.Lldp.Misc = o.Misc["Layer3Lldp"]
+ }
+ if o.Layer3.Lldp.Enable != nil {
+ nestedLayer3.Lldp.Enable = util.YesNo(o.Layer3.Lldp.Enable, nil)
+ }
+ if o.Layer3.Lldp.Profile != nil {
+ nestedLayer3.Lldp.Profile = o.Layer3.Lldp.Profile
+ }
+ }
+ if o.Layer3.Mtu != nil {
+ nestedLayer3.Mtu = o.Layer3.Mtu
+ }
+ if o.Layer3.SdwanLinkSettings != nil {
+ nestedLayer3.SdwanLinkSettings = &Layer3SdwanLinkSettingsXml{}
+ if _, ok := o.Misc["Layer3SdwanLinkSettings"]; ok {
+ nestedLayer3.SdwanLinkSettings.Misc = o.Misc["Layer3SdwanLinkSettings"]
+ }
+ if o.Layer3.SdwanLinkSettings.SdwanInterfaceProfile != nil {
+ nestedLayer3.SdwanLinkSettings.SdwanInterfaceProfile = o.Layer3.SdwanLinkSettings.SdwanInterfaceProfile
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat = &Layer3SdwanLinkSettingsUpstreamNatXml{}
+ if _, ok := o.Misc["Layer3SdwanLinkSettingsUpstreamNat"]; ok {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat.Misc = o.Misc["Layer3SdwanLinkSettingsUpstreamNat"]
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat.StaticIp != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat.StaticIp = o.Layer3.SdwanLinkSettings.UpstreamNat.StaticIp
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat.Enable != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat.Enable = util.YesNo(o.Layer3.SdwanLinkSettings.UpstreamNat.Enable, nil)
+ }
+ }
+ if o.Layer3.SdwanLinkSettings.Enable != nil {
+ nestedLayer3.SdwanLinkSettings.Enable = util.YesNo(o.Layer3.SdwanLinkSettings.Enable, nil)
+ }
+ }
+ if o.Layer3.UntaggedSubInterface != nil {
+ nestedLayer3.UntaggedSubInterface = util.YesNo(o.Layer3.UntaggedSubInterface, nil)
+ }
+ if o.Layer3.DhcpClient != nil {
+ nestedLayer3.DhcpClient = &Layer3DhcpClientXml{}
+ if _, ok := o.Misc["Layer3DhcpClient"]; ok {
+ nestedLayer3.DhcpClient.Misc = o.Misc["Layer3DhcpClient"]
+ }
+ if o.Layer3.DhcpClient.Enable != nil {
+ nestedLayer3.DhcpClient.Enable = util.YesNo(o.Layer3.DhcpClient.Enable, nil)
+ }
+ if o.Layer3.DhcpClient.CreateDefaultRoute != nil {
+ nestedLayer3.DhcpClient.CreateDefaultRoute = util.YesNo(o.Layer3.DhcpClient.CreateDefaultRoute, nil)
+ }
+ if o.Layer3.DhcpClient.DefaultRouteMetric != nil {
+ nestedLayer3.DhcpClient.DefaultRouteMetric = o.Layer3.DhcpClient.DefaultRouteMetric
+ }
+ if o.Layer3.DhcpClient.SendHostname != nil {
+ nestedLayer3.DhcpClient.SendHostname = &Layer3DhcpClientSendHostnameXml{}
+ if _, ok := o.Misc["Layer3DhcpClientSendHostname"]; ok {
+ nestedLayer3.DhcpClient.SendHostname.Misc = o.Misc["Layer3DhcpClientSendHostname"]
+ }
+ if o.Layer3.DhcpClient.SendHostname.Hostname != nil {
+ nestedLayer3.DhcpClient.SendHostname.Hostname = o.Layer3.DhcpClient.SendHostname.Hostname
+ }
+ if o.Layer3.DhcpClient.SendHostname.Enable != nil {
+ nestedLayer3.DhcpClient.SendHostname.Enable = util.YesNo(o.Layer3.DhcpClient.SendHostname.Enable, nil)
+ }
+ }
+ }
+ if o.Layer3.InterfaceManagementProfile != nil {
+ nestedLayer3.InterfaceManagementProfile = o.Layer3.InterfaceManagementProfile
+ }
+ if o.Layer3.NetflowProfile != nil {
+ nestedLayer3.NetflowProfile = o.Layer3.NetflowProfile
+ }
+ if o.Layer3.Bonjour != nil {
+ nestedLayer3.Bonjour = &Layer3BonjourXml{}
+ if _, ok := o.Misc["Layer3Bonjour"]; ok {
+ nestedLayer3.Bonjour.Misc = o.Misc["Layer3Bonjour"]
+ }
+ if o.Layer3.Bonjour.Enable != nil {
+ nestedLayer3.Bonjour.Enable = util.YesNo(o.Layer3.Bonjour.Enable, nil)
+ }
+ }
+ }
+ entry.Layer3 = nestedLayer3
+
+ var nestedTap *TapXml
+ if o.Tap != nil {
+ nestedTap = &TapXml{}
+ if _, ok := o.Misc["Tap"]; ok {
+ nestedTap.Misc = o.Misc["Tap"]
+ }
+ if o.Tap.NetflowProfile != nil {
+ nestedTap.NetflowProfile = o.Tap.NetflowProfile
+ }
+ }
+ entry.Tap = nestedTap
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Comment = o.Comment
+ entry.LinkDuplex = o.LinkDuplex
+ entry.LinkSpeed = o.LinkSpeed
+ entry.LinkState = o.LinkState
+ var nestedPoe *Poe
+ if o.Poe != nil {
+ nestedPoe = &Poe{}
+ if o.Poe.Misc != nil {
+ entry.Misc["Poe"] = o.Poe.Misc
+ }
+ if o.Poe.ReservedPower != nil {
+ nestedPoe.ReservedPower = o.Poe.ReservedPower
+ }
+ if o.Poe.Enabled != nil {
+ nestedPoe.Enabled = util.AsBool(o.Poe.Enabled, nil)
+ }
+ }
+ entry.Poe = nestedPoe
+
+ var nestedHa *Ha
+ if o.Ha != nil {
+ nestedHa = &Ha{}
+ if o.Ha.Misc != nil {
+ entry.Misc["Ha"] = o.Ha.Misc
+ }
+ }
+ entry.Ha = nestedHa
+
+ var nestedLayer3 *Layer3
+ if o.Layer3 != nil {
+ nestedLayer3 = &Layer3{}
+ if o.Layer3.Misc != nil {
+ entry.Misc["Layer3"] = o.Layer3.Misc
+ }
+ if o.Layer3.Bonjour != nil {
+ nestedLayer3.Bonjour = &Layer3Bonjour{}
+ if o.Layer3.Bonjour.Misc != nil {
+ entry.Misc["Layer3Bonjour"] = o.Layer3.Bonjour.Misc
+ }
+ if o.Layer3.Bonjour.Enable != nil {
+ nestedLayer3.Bonjour.Enable = util.AsBool(o.Layer3.Bonjour.Enable, nil)
+ }
+ }
+ if o.Layer3.SdwanLinkSettings != nil {
+ nestedLayer3.SdwanLinkSettings = &Layer3SdwanLinkSettings{}
+ if o.Layer3.SdwanLinkSettings.Misc != nil {
+ entry.Misc["Layer3SdwanLinkSettings"] = o.Layer3.SdwanLinkSettings.Misc
+ }
+ if o.Layer3.SdwanLinkSettings.Enable != nil {
+ nestedLayer3.SdwanLinkSettings.Enable = util.AsBool(o.Layer3.SdwanLinkSettings.Enable, nil)
+ }
+ if o.Layer3.SdwanLinkSettings.SdwanInterfaceProfile != nil {
+ nestedLayer3.SdwanLinkSettings.SdwanInterfaceProfile = o.Layer3.SdwanLinkSettings.SdwanInterfaceProfile
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat = &Layer3SdwanLinkSettingsUpstreamNat{}
+ if o.Layer3.SdwanLinkSettings.UpstreamNat.Misc != nil {
+ entry.Misc["Layer3SdwanLinkSettingsUpstreamNat"] = o.Layer3.SdwanLinkSettings.UpstreamNat.Misc
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat.Enable != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat.Enable = util.AsBool(o.Layer3.SdwanLinkSettings.UpstreamNat.Enable, nil)
+ }
+ if o.Layer3.SdwanLinkSettings.UpstreamNat.StaticIp != nil {
+ nestedLayer3.SdwanLinkSettings.UpstreamNat.StaticIp = o.Layer3.SdwanLinkSettings.UpstreamNat.StaticIp
+ }
+ }
+ }
+ if o.Layer3.UntaggedSubInterface != nil {
+ nestedLayer3.UntaggedSubInterface = util.AsBool(o.Layer3.UntaggedSubInterface, nil)
+ }
+ if o.Layer3.DhcpClient != nil {
+ nestedLayer3.DhcpClient = &Layer3DhcpClient{}
+ if o.Layer3.DhcpClient.Misc != nil {
+ entry.Misc["Layer3DhcpClient"] = o.Layer3.DhcpClient.Misc
+ }
+ if o.Layer3.DhcpClient.Enable != nil {
+ nestedLayer3.DhcpClient.Enable = util.AsBool(o.Layer3.DhcpClient.Enable, nil)
+ }
+ if o.Layer3.DhcpClient.CreateDefaultRoute != nil {
+ nestedLayer3.DhcpClient.CreateDefaultRoute = util.AsBool(o.Layer3.DhcpClient.CreateDefaultRoute, nil)
+ }
+ if o.Layer3.DhcpClient.DefaultRouteMetric != nil {
+ nestedLayer3.DhcpClient.DefaultRouteMetric = o.Layer3.DhcpClient.DefaultRouteMetric
+ }
+ if o.Layer3.DhcpClient.SendHostname != nil {
+ nestedLayer3.DhcpClient.SendHostname = &Layer3DhcpClientSendHostname{}
+ if o.Layer3.DhcpClient.SendHostname.Misc != nil {
+ entry.Misc["Layer3DhcpClientSendHostname"] = o.Layer3.DhcpClient.SendHostname.Misc
+ }
+ if o.Layer3.DhcpClient.SendHostname.Enable != nil {
+ nestedLayer3.DhcpClient.SendHostname.Enable = util.AsBool(o.Layer3.DhcpClient.SendHostname.Enable, nil)
+ }
+ if o.Layer3.DhcpClient.SendHostname.Hostname != nil {
+ nestedLayer3.DhcpClient.SendHostname.Hostname = o.Layer3.DhcpClient.SendHostname.Hostname
+ }
+ }
+ }
+ if o.Layer3.InterfaceManagementProfile != nil {
+ nestedLayer3.InterfaceManagementProfile = o.Layer3.InterfaceManagementProfile
+ }
+ if o.Layer3.NetflowProfile != nil {
+ nestedLayer3.NetflowProfile = o.Layer3.NetflowProfile
+ }
+ if o.Layer3.Mtu != nil {
+ nestedLayer3.Mtu = o.Layer3.Mtu
+ }
+ if o.Layer3.Ips != nil {
+ nestedLayer3.Ips = []Layer3Ips{}
+ for _, oLayer3Ips := range o.Layer3.Ips {
+ nestedLayer3Ips := Layer3Ips{}
+ if oLayer3Ips.Misc != nil {
+ entry.Misc["Layer3Ips"] = oLayer3Ips.Misc
+ }
+ if oLayer3Ips.SdwanGateway != nil {
+ nestedLayer3Ips.SdwanGateway = oLayer3Ips.SdwanGateway
+ }
+ if oLayer3Ips.Name != "" {
+ nestedLayer3Ips.Name = oLayer3Ips.Name
+ }
+ nestedLayer3.Ips = append(nestedLayer3.Ips, nestedLayer3Ips)
+ }
+ }
+ if o.Layer3.Ipv6 != nil {
+ nestedLayer3.Ipv6 = &Layer3Ipv6{}
+ if o.Layer3.Ipv6.Misc != nil {
+ entry.Misc["Layer3Ipv6"] = o.Layer3.Ipv6.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer != nil {
+ nestedLayer3.Ipv6.DnsServer = &Layer3Ipv6DnsServer{}
+ if o.Layer3.Ipv6.DnsServer.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServer"] = o.Layer3.Ipv6.DnsServer.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport = &Layer3Ipv6DnsServerDnsSupport{}
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerDnsSupport"] = o.Layer3.Ipv6.DnsServer.DnsSupport.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Enable != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Enable = util.AsBool(o.Layer3.Ipv6.DnsServer.DnsSupport.Enable, nil)
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Server != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Server = []Layer3Ipv6DnsServerDnsSupportServer{}
+ for _, oLayer3Ipv6DnsServerDnsSupportServer := range o.Layer3.Ipv6.DnsServer.DnsSupport.Server {
+ nestedLayer3Ipv6DnsServerDnsSupportServer := Layer3Ipv6DnsServerDnsSupportServer{}
+ if oLayer3Ipv6DnsServerDnsSupportServer.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerDnsSupportServer"] = oLayer3Ipv6DnsServerDnsSupportServer.Misc
+ }
+ if oLayer3Ipv6DnsServerDnsSupportServer.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerDnsSupportServer.Lifetime = oLayer3Ipv6DnsServerDnsSupportServer.Lifetime
+ }
+ if oLayer3Ipv6DnsServerDnsSupportServer.Name != "" {
+ nestedLayer3Ipv6DnsServerDnsSupportServer.Name = oLayer3Ipv6DnsServerDnsSupportServer.Name
+ }
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Server = append(nestedLayer3.Ipv6.DnsServer.DnsSupport.Server, nestedLayer3Ipv6DnsServerDnsSupportServer)
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.DnsSupport.Suffix != nil {
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix = []Layer3Ipv6DnsServerDnsSupportSuffix{}
+ for _, oLayer3Ipv6DnsServerDnsSupportSuffix := range o.Layer3.Ipv6.DnsServer.DnsSupport.Suffix {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix := Layer3Ipv6DnsServerDnsSupportSuffix{}
+ if oLayer3Ipv6DnsServerDnsSupportSuffix.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerDnsSupportSuffix"] = oLayer3Ipv6DnsServerDnsSupportSuffix.Misc
+ }
+ if oLayer3Ipv6DnsServerDnsSupportSuffix.Name != "" {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix.Name = oLayer3Ipv6DnsServerDnsSupportSuffix.Name
+ }
+ if oLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime = oLayer3Ipv6DnsServerDnsSupportSuffix.Lifetime
+ }
+ nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix = append(nestedLayer3.Ipv6.DnsServer.DnsSupport.Suffix, nestedLayer3Ipv6DnsServerDnsSupportSuffix)
+ }
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.Enable != nil {
+ nestedLayer3.Ipv6.DnsServer.Enable = util.AsBool(o.Layer3.Ipv6.DnsServer.Enable, nil)
+ }
+ if o.Layer3.Ipv6.DnsServer.Source != nil {
+ nestedLayer3.Ipv6.DnsServer.Source = &Layer3Ipv6DnsServerSource{}
+ if o.Layer3.Ipv6.DnsServer.Source.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerSource"] = o.Layer3.Ipv6.DnsServer.Source.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Dhcpv6 != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Dhcpv6 = &Layer3Ipv6DnsServerSourceDhcpv6{}
+ if o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerSourceDhcpv6"] = o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool = o.Layer3.Ipv6.DnsServer.Source.Dhcpv6.PrefixPool
+ }
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Manual != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Manual = &Layer3Ipv6DnsServerSourceManual{}
+ if o.Layer3.Ipv6.DnsServer.Source.Manual.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerSourceManual"] = o.Layer3.Ipv6.DnsServer.Source.Manual.Misc
+ }
+ if o.Layer3.Ipv6.DnsServer.Source.Manual.Suffix != nil {
+ nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix = []Layer3Ipv6DnsServerSourceManualSuffix{}
+ for _, oLayer3Ipv6DnsServerSourceManualSuffix := range o.Layer3.Ipv6.DnsServer.Source.Manual.Suffix {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix := Layer3Ipv6DnsServerSourceManualSuffix{}
+ if oLayer3Ipv6DnsServerSourceManualSuffix.Misc != nil {
+ entry.Misc["Layer3Ipv6DnsServerSourceManualSuffix"] = oLayer3Ipv6DnsServerSourceManualSuffix.Misc
+ }
+ if oLayer3Ipv6DnsServerSourceManualSuffix.Lifetime != nil {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix.Lifetime = oLayer3Ipv6DnsServerSourceManualSuffix.Lifetime
+ }
+ if oLayer3Ipv6DnsServerSourceManualSuffix.Name != "" {
+ nestedLayer3Ipv6DnsServerSourceManualSuffix.Name = oLayer3Ipv6DnsServerSourceManualSuffix.Name
+ }
+ nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix = append(nestedLayer3.Ipv6.DnsServer.Source.Manual.Suffix, nestedLayer3Ipv6DnsServerSourceManualSuffix)
+ }
+ }
+ }
+ }
+ }
+ if o.Layer3.Ipv6.Enabled != nil {
+ nestedLayer3.Ipv6.Enabled = util.AsBool(o.Layer3.Ipv6.Enabled, nil)
+ }
+ if o.Layer3.Ipv6.InterfaceId != nil {
+ nestedLayer3.Ipv6.InterfaceId = o.Layer3.Ipv6.InterfaceId
+ }
+ if o.Layer3.Ipv6.Addresses != nil {
+ nestedLayer3.Ipv6.Addresses = []Layer3Ipv6Addresses{}
+ for _, oLayer3Ipv6Addresses := range o.Layer3.Ipv6.Addresses {
+ nestedLayer3Ipv6Addresses := Layer3Ipv6Addresses{}
+ if oLayer3Ipv6Addresses.Misc != nil {
+ entry.Misc["Layer3Ipv6Addresses"] = oLayer3Ipv6Addresses.Misc
+ }
+ if oLayer3Ipv6Addresses.EnableOnInterface != nil {
+ nestedLayer3Ipv6Addresses.EnableOnInterface = util.AsBool(oLayer3Ipv6Addresses.EnableOnInterface, nil)
+ }
+ if oLayer3Ipv6Addresses.Prefix != nil {
+ nestedLayer3Ipv6Addresses.Prefix = oLayer3Ipv6Addresses.Prefix
+ }
+ if oLayer3Ipv6Addresses.Anycast != nil {
+ nestedLayer3Ipv6Addresses.Anycast = oLayer3Ipv6Addresses.Anycast
+ }
+ if oLayer3Ipv6Addresses.Advertise != nil {
+ nestedLayer3Ipv6Addresses.Advertise = &Layer3Ipv6AddressesAdvertise{}
+ if oLayer3Ipv6Addresses.Advertise.Misc != nil {
+ entry.Misc["Layer3Ipv6AddressesAdvertise"] = oLayer3Ipv6Addresses.Advertise.Misc
+ }
+ if oLayer3Ipv6Addresses.Advertise.Enable != nil {
+ nestedLayer3Ipv6Addresses.Advertise.Enable = util.AsBool(oLayer3Ipv6Addresses.Advertise.Enable, nil)
+ }
+ if oLayer3Ipv6Addresses.Advertise.ValidLifetime != nil {
+ nestedLayer3Ipv6Addresses.Advertise.ValidLifetime = oLayer3Ipv6Addresses.Advertise.ValidLifetime
+ }
+ if oLayer3Ipv6Addresses.Advertise.PreferredLifetime != nil {
+ nestedLayer3Ipv6Addresses.Advertise.PreferredLifetime = oLayer3Ipv6Addresses.Advertise.PreferredLifetime
+ }
+ if oLayer3Ipv6Addresses.Advertise.OnlinkFlag != nil {
+ nestedLayer3Ipv6Addresses.Advertise.OnlinkFlag = util.AsBool(oLayer3Ipv6Addresses.Advertise.OnlinkFlag, nil)
+ }
+ if oLayer3Ipv6Addresses.Advertise.AutoConfigFlag != nil {
+ nestedLayer3Ipv6Addresses.Advertise.AutoConfigFlag = util.AsBool(oLayer3Ipv6Addresses.Advertise.AutoConfigFlag, nil)
+ }
+ }
+ if oLayer3Ipv6Addresses.Name != "" {
+ nestedLayer3Ipv6Addresses.Name = oLayer3Ipv6Addresses.Name
+ }
+ nestedLayer3.Ipv6.Addresses = append(nestedLayer3.Ipv6.Addresses, nestedLayer3Ipv6Addresses)
+ }
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery = &Layer3Ipv6NeighborDiscovery{}
+ if o.Layer3.Ipv6.NeighborDiscovery.Misc != nil {
+ entry.Misc["Layer3Ipv6NeighborDiscovery"] = o.Layer3.Ipv6.NeighborDiscovery.Misc
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.EnableDad != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.EnableDad = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.EnableDad, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.DadAttempts != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.DadAttempts = o.Layer3.Ipv6.NeighborDiscovery.DadAttempts
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.NsInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.NsInterval = o.Layer3.Ipv6.NeighborDiscovery.NsInterval
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.ReachableTime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.ReachableTime = o.Layer3.Ipv6.NeighborDiscovery.ReachableTime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement = &Layer3Ipv6NeighborDiscoveryRouterAdvertisement{}
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Misc != nil {
+ entry.Misc["Layer3Ipv6NeighborDiscoveryRouterAdvertisement"] = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Misc
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.OtherFlag, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.EnableConsistencyCheck, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.LinkMtu
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RetransmissionTimer
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MinInterval
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ReachableTime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.HopLimit
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Lifetime
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.RouterPreference
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.ManagedFlag, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.Enable, nil)
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval = o.Layer3.Ipv6.NeighborDiscovery.RouterAdvertisement.MaxInterval
+ }
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.Neighbor != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.Neighbor = []Layer3Ipv6NeighborDiscoveryNeighbor{}
+ for _, oLayer3Ipv6NeighborDiscoveryNeighbor := range o.Layer3.Ipv6.NeighborDiscovery.Neighbor {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor := Layer3Ipv6NeighborDiscoveryNeighbor{}
+ if oLayer3Ipv6NeighborDiscoveryNeighbor.Misc != nil {
+ entry.Misc["Layer3Ipv6NeighborDiscoveryNeighbor"] = oLayer3Ipv6NeighborDiscoveryNeighbor.Misc
+ }
+ if oLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress != nil {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress = oLayer3Ipv6NeighborDiscoveryNeighbor.HwAddress
+ }
+ if oLayer3Ipv6NeighborDiscoveryNeighbor.Name != "" {
+ nestedLayer3Ipv6NeighborDiscoveryNeighbor.Name = oLayer3Ipv6NeighborDiscoveryNeighbor.Name
+ }
+ nestedLayer3.Ipv6.NeighborDiscovery.Neighbor = append(nestedLayer3.Ipv6.NeighborDiscovery.Neighbor, nestedLayer3Ipv6NeighborDiscoveryNeighbor)
+ }
+ }
+ if o.Layer3.Ipv6.NeighborDiscovery.EnableNdpMonitor != nil {
+ nestedLayer3.Ipv6.NeighborDiscovery.EnableNdpMonitor = util.AsBool(o.Layer3.Ipv6.NeighborDiscovery.EnableNdpMonitor, nil)
+ }
+ }
+ }
+ if o.Layer3.AdjustTcpMss != nil {
+ nestedLayer3.AdjustTcpMss = &Layer3AdjustTcpMss{}
+ if o.Layer3.AdjustTcpMss.Misc != nil {
+ entry.Misc["Layer3AdjustTcpMss"] = o.Layer3.AdjustTcpMss.Misc
+ }
+ if o.Layer3.AdjustTcpMss.Ipv4MssAdjustment != nil {
+ nestedLayer3.AdjustTcpMss.Ipv4MssAdjustment = o.Layer3.AdjustTcpMss.Ipv4MssAdjustment
+ }
+ if o.Layer3.AdjustTcpMss.Ipv6MssAdjustment != nil {
+ nestedLayer3.AdjustTcpMss.Ipv6MssAdjustment = o.Layer3.AdjustTcpMss.Ipv6MssAdjustment
+ }
+ if o.Layer3.AdjustTcpMss.Enable != nil {
+ nestedLayer3.AdjustTcpMss.Enable = util.AsBool(o.Layer3.AdjustTcpMss.Enable, nil)
+ }
+ }
+ if o.Layer3.Arp != nil {
+ nestedLayer3.Arp = []Layer3Arp{}
+ for _, oLayer3Arp := range o.Layer3.Arp {
+ nestedLayer3Arp := Layer3Arp{}
+ if oLayer3Arp.Misc != nil {
+ entry.Misc["Layer3Arp"] = oLayer3Arp.Misc
+ }
+ if oLayer3Arp.HwAddress != nil {
+ nestedLayer3Arp.HwAddress = oLayer3Arp.HwAddress
+ }
+ if oLayer3Arp.Name != "" {
+ nestedLayer3Arp.Name = oLayer3Arp.Name
+ }
+ nestedLayer3.Arp = append(nestedLayer3.Arp, nestedLayer3Arp)
+ }
+ }
+ if o.Layer3.NdpProxy != nil {
+ nestedLayer3.NdpProxy = util.AsBool(o.Layer3.NdpProxy, nil)
+ }
+ if o.Layer3.Lldp != nil {
+ nestedLayer3.Lldp = &Layer3Lldp{}
+ if o.Layer3.Lldp.Misc != nil {
+ entry.Misc["Layer3Lldp"] = o.Layer3.Lldp.Misc
+ }
+ if o.Layer3.Lldp.Enable != nil {
+ nestedLayer3.Lldp.Enable = util.AsBool(o.Layer3.Lldp.Enable, nil)
+ }
+ if o.Layer3.Lldp.Profile != nil {
+ nestedLayer3.Lldp.Profile = o.Layer3.Lldp.Profile
+ }
+ }
+ }
+ entry.Layer3 = nestedLayer3
+
+ var nestedTap *Tap
+ if o.Tap != nil {
+ nestedTap = &Tap{}
+ if o.Tap.Misc != nil {
+ entry.Misc["Tap"] = o.Tap.Misc
+ }
+ if o.Tap.NetflowProfile != nil {
+ nestedTap.NetflowProfile = o.Tap.NetflowProfile
+ }
+ }
+ entry.Tap = nestedTap
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Comment, b.Comment) {
+ return false
+ }
+ if !util.StringsMatch(a.LinkDuplex, b.LinkDuplex) {
+ return false
+ }
+ if !util.StringsMatch(a.LinkSpeed, b.LinkSpeed) {
+ return false
+ }
+ if !util.StringsMatch(a.LinkState, b.LinkState) {
+ return false
+ }
+ if !matchPoe(a.Poe, b.Poe) {
+ return false
+ }
+ if !matchHa(a.Ha, b.Ha) {
+ return false
+ }
+ if !matchLayer3(a.Layer3, b.Layer3) {
+ return false
+ }
+ if !matchTap(a.Tap, b.Tap) {
+ return false
+ }
+
+ return true
+}
+
+func matchPoe(a *Poe, b *Poe) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.ReservedPower, b.ReservedPower) {
+ return false
+ }
+ if !util.BoolsMatch(a.Enabled, b.Enabled) {
+ return false
+ }
+ return true
+}
+func matchHa(a *Ha, b *Ha) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ return true
+}
+func matchLayer3DhcpClientSendHostname(a *Layer3DhcpClientSendHostname, b *Layer3DhcpClientSendHostname) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.StringsMatch(a.Hostname, b.Hostname) {
+ return false
+ }
+ return true
+}
+func matchLayer3DhcpClient(a *Layer3DhcpClient, b *Layer3DhcpClient) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchLayer3DhcpClientSendHostname(a.SendHostname, b.SendHostname) {
+ return false
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.BoolsMatch(a.CreateDefaultRoute, b.CreateDefaultRoute) {
+ return false
+ }
+ if !util.Ints64Match(a.DefaultRouteMetric, b.DefaultRouteMetric) {
+ return false
+ }
+ return true
+}
+func matchLayer3Bonjour(a *Layer3Bonjour, b *Layer3Bonjour) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchLayer3SdwanLinkSettingsUpstreamNat(a *Layer3SdwanLinkSettingsUpstreamNat, b *Layer3SdwanLinkSettingsUpstreamNat) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.StringsMatch(a.StaticIp, b.StaticIp) {
+ return false
+ }
+ return true
+}
+func matchLayer3SdwanLinkSettings(a *Layer3SdwanLinkSettings, b *Layer3SdwanLinkSettings) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.StringsMatch(a.SdwanInterfaceProfile, b.SdwanInterfaceProfile) {
+ return false
+ }
+ if !matchLayer3SdwanLinkSettingsUpstreamNat(a.UpstreamNat, b.UpstreamNat) {
+ return false
+ }
+ return true
+}
+func matchLayer3AdjustTcpMss(a *Layer3AdjustTcpMss, b *Layer3AdjustTcpMss) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.Ipv4MssAdjustment, b.Ipv4MssAdjustment) {
+ return false
+ }
+ if !util.Ints64Match(a.Ipv6MssAdjustment, b.Ipv6MssAdjustment) {
+ return false
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchLayer3Arp(a []Layer3Arp, b []Layer3Arp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.StringsMatch(a.HwAddress, b.HwAddress) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Lldp(a *Layer3Lldp, b *Layer3Lldp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.StringsMatch(a.Profile, b.Profile) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ips(a []Layer3Ips, b []Layer3Ips) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.StringsMatch(a.SdwanGateway, b.SdwanGateway) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6AddressesAdvertise(a *Layer3Ipv6AddressesAdvertise, b *Layer3Ipv6AddressesAdvertise) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.AutoConfigFlag, b.AutoConfigFlag) {
+ return false
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.StringsMatch(a.ValidLifetime, b.ValidLifetime) {
+ return false
+ }
+ if !util.StringsMatch(a.PreferredLifetime, b.PreferredLifetime) {
+ return false
+ }
+ if !util.BoolsMatch(a.OnlinkFlag, b.OnlinkFlag) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6Addresses(a []Layer3Ipv6Addresses, b []Layer3Ipv6Addresses) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.BoolsMatch(a.EnableOnInterface, b.EnableOnInterface) {
+ return false
+ }
+ if !util.StringsMatch(a.Prefix, b.Prefix) {
+ return false
+ }
+ if !util.StringsMatch(a.Anycast, b.Anycast) {
+ return false
+ }
+ if !matchLayer3Ipv6AddressesAdvertise(a.Advertise, b.Advertise) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6NeighborDiscoveryRouterAdvertisement(a *Layer3Ipv6NeighborDiscoveryRouterAdvertisement, b *Layer3Ipv6NeighborDiscoveryRouterAdvertisement) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.HopLimit, b.HopLimit) {
+ return false
+ }
+ if !util.Ints64Match(a.Lifetime, b.Lifetime) {
+ return false
+ }
+ if !util.StringsMatch(a.RouterPreference, b.RouterPreference) {
+ return false
+ }
+ if !util.BoolsMatch(a.ManagedFlag, b.ManagedFlag) {
+ return false
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.Ints64Match(a.MaxInterval, b.MaxInterval) {
+ return false
+ }
+ if !util.Ints64Match(a.MinInterval, b.MinInterval) {
+ return false
+ }
+ if !util.StringsMatch(a.ReachableTime, b.ReachableTime) {
+ return false
+ }
+ if !util.StringsMatch(a.LinkMtu, b.LinkMtu) {
+ return false
+ }
+ if !util.StringsMatch(a.RetransmissionTimer, b.RetransmissionTimer) {
+ return false
+ }
+ if !util.BoolsMatch(a.OtherFlag, b.OtherFlag) {
+ return false
+ }
+ if !util.BoolsMatch(a.EnableConsistencyCheck, b.EnableConsistencyCheck) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6NeighborDiscoveryNeighbor(a []Layer3Ipv6NeighborDiscoveryNeighbor, b []Layer3Ipv6NeighborDiscoveryNeighbor) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.StringsMatch(a.HwAddress, b.HwAddress) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6NeighborDiscovery(a *Layer3Ipv6NeighborDiscovery, b *Layer3Ipv6NeighborDiscovery) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchLayer3Ipv6NeighborDiscoveryNeighbor(a.Neighbor, b.Neighbor) {
+ return false
+ }
+ if !util.BoolsMatch(a.EnableNdpMonitor, b.EnableNdpMonitor) {
+ return false
+ }
+ if !util.BoolsMatch(a.EnableDad, b.EnableDad) {
+ return false
+ }
+ if !util.Ints64Match(a.DadAttempts, b.DadAttempts) {
+ return false
+ }
+ if !util.Ints64Match(a.NsInterval, b.NsInterval) {
+ return false
+ }
+ if !util.Ints64Match(a.ReachableTime, b.ReachableTime) {
+ return false
+ }
+ if !matchLayer3Ipv6NeighborDiscoveryRouterAdvertisement(a.RouterAdvertisement, b.RouterAdvertisement) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerSourceManualSuffix(a []Layer3Ipv6DnsServerSourceManualSuffix, b []Layer3Ipv6DnsServerSourceManualSuffix) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.Ints64Match(a.Lifetime, b.Lifetime) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerSourceManual(a *Layer3Ipv6DnsServerSourceManual, b *Layer3Ipv6DnsServerSourceManual) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchLayer3Ipv6DnsServerSourceManualSuffix(a.Suffix, b.Suffix) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerSourceDhcpv6(a *Layer3Ipv6DnsServerSourceDhcpv6, b *Layer3Ipv6DnsServerSourceDhcpv6) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.PrefixPool, b.PrefixPool) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerSource(a *Layer3Ipv6DnsServerSource, b *Layer3Ipv6DnsServerSource) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchLayer3Ipv6DnsServerSourceDhcpv6(a.Dhcpv6, b.Dhcpv6) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServerSourceManual(a.Manual, b.Manual) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerDnsSupportServer(a []Layer3Ipv6DnsServerDnsSupportServer, b []Layer3Ipv6DnsServerDnsSupportServer) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.Ints64Match(a.Lifetime, b.Lifetime) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerDnsSupportSuffix(a []Layer3Ipv6DnsServerDnsSupportSuffix, b []Layer3Ipv6DnsServerDnsSupportSuffix) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ if !util.Ints64Match(a.Lifetime, b.Lifetime) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServerDnsSupport(a *Layer3Ipv6DnsServerDnsSupport, b *Layer3Ipv6DnsServerDnsSupport) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServerDnsSupportServer(a.Server, b.Server) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServerDnsSupportSuffix(a.Suffix, b.Suffix) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6DnsServer(a *Layer3Ipv6DnsServer, b *Layer3Ipv6DnsServer) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServerSource(a.Source, b.Source) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServerDnsSupport(a.DnsSupport, b.DnsSupport) {
+ return false
+ }
+ return true
+}
+func matchLayer3Ipv6(a *Layer3Ipv6, b *Layer3Ipv6) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enabled, b.Enabled) {
+ return false
+ }
+ if !util.StringsMatch(a.InterfaceId, b.InterfaceId) {
+ return false
+ }
+ if !matchLayer3Ipv6Addresses(a.Addresses, b.Addresses) {
+ return false
+ }
+ if !matchLayer3Ipv6NeighborDiscovery(a.NeighborDiscovery, b.NeighborDiscovery) {
+ return false
+ }
+ if !matchLayer3Ipv6DnsServer(a.DnsServer, b.DnsServer) {
+ return false
+ }
+ return true
+}
+func matchLayer3(a *Layer3, b *Layer3) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchLayer3DhcpClient(a.DhcpClient, b.DhcpClient) {
+ return false
+ }
+ if !util.StringsMatch(a.InterfaceManagementProfile, b.InterfaceManagementProfile) {
+ return false
+ }
+ if !util.StringsMatch(a.NetflowProfile, b.NetflowProfile) {
+ return false
+ }
+ if !matchLayer3Bonjour(a.Bonjour, b.Bonjour) {
+ return false
+ }
+ if !matchLayer3SdwanLinkSettings(a.SdwanLinkSettings, b.SdwanLinkSettings) {
+ return false
+ }
+ if !util.BoolsMatch(a.UntaggedSubInterface, b.UntaggedSubInterface) {
+ return false
+ }
+ if !matchLayer3AdjustTcpMss(a.AdjustTcpMss, b.AdjustTcpMss) {
+ return false
+ }
+ if !matchLayer3Arp(a.Arp, b.Arp) {
+ return false
+ }
+ if !util.BoolsMatch(a.NdpProxy, b.NdpProxy) {
+ return false
+ }
+ if !matchLayer3Lldp(a.Lldp, b.Lldp) {
+ return false
+ }
+ if !util.Ints64Match(a.Mtu, b.Mtu) {
+ return false
+ }
+ if !matchLayer3Ips(a.Ips, b.Ips) {
+ return false
+ }
+ if !matchLayer3Ipv6(a.Ipv6, b.Ipv6) {
+ return false
+ }
+ return true
+}
+func matchTap(a *Tap, b *Tap) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.NetflowProfile, b.NetflowProfile) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/network/interface/ethernet/interfaces.go b/network/interface/ethernet/interfaces.go
new file mode 100644
index 0000000..45b962d
--- /dev/null
+++ b/network/interface/ethernet/interfaces.go
@@ -0,0 +1,7 @@
+package ethernet
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/network/interface/ethernet/location.go b/network/interface/ethernet/location.go
new file mode 100644
index 0000000..e64d895
--- /dev/null
+++ b/network/interface/ethernet/location.go
@@ -0,0 +1,606 @@
+package ethernet
+
+import (
+ "encoding/xml"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+type Layer3TemplateType int
+
+const (
+ layer3TemplateVsys Layer3TemplateType = iota
+ layer3TemplateZone Layer3TemplateType = iota
+ layer3TemplateVirtualRouter Layer3TemplateType = iota
+ layer3TemplateLogicalRouter Layer3TemplateType = iota
+)
+
+type Layer3TemplateImportLocation struct {
+ typ Layer3TemplateType
+ vsys *Layer3TemplateVsysImportLocation
+ zone *Layer3TemplateZoneImportLocation
+ virtualRouter *Layer3TemplateVirtualRouterImportLocation
+ logicalRouter *Layer3TemplateLogicalRouterImportLocation
+}
+
+type Layer3TemplateVsysImportLocation struct {
+ xpath []string
+ vsys string
+}
+
+type Layer3TemplateVsysImportLocationSpec struct {
+ Vsys string
+}
+
+func NewLayer3TemplateVsysImportLocation(spec Layer3TemplateVsysImportLocationSpec) *Layer3TemplateImportLocation {
+ location := &Layer3TemplateVsysImportLocation{
+ vsys: spec.Vsys,
+ }
+
+ return &Layer3TemplateImportLocation{
+ typ: layer3TemplateVsys,
+ vsys: location,
+ }
+}
+
+func (o *Layer3TemplateVsysImportLocation) XpathForLocation(vn version.Number, loc util.ILocation) ([]string, error) {
+ ans, err := loc.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ importAns := []string{
+ "vsys",
+ util.AsEntryXpath([]string{o.vsys}),
+ "import",
+ "network",
+ "interface",
+ }
+
+ return append(ans, importAns...), nil
+}
+
+func (o *Layer3TemplateVsysImportLocation) MarshalPangoXML(interfaces []string) (string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type request struct {
+ XMLName xml.Name `xml:"interface"`
+ Members []member `xml:"member"`
+ }
+
+ var members []member
+ for _, elt := range interfaces {
+ members = append(members, member{Name: elt})
+ }
+
+ expected := request{
+ Members: members,
+ }
+ bytes, err := xml.Marshal(expected)
+ if err != nil {
+ return "", err
+ }
+
+ return string(bytes), nil
+}
+
+func (o *Layer3TemplateVsysImportLocation) UnmarshalPangoXML(bytes []byte) ([]string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type response struct {
+ Members []member `xml:"result>interface>member"`
+ }
+
+ var existing response
+ err := xml.Unmarshal(bytes, &existing)
+ if err != nil {
+ return nil, err
+ }
+
+ var interfaces []string
+ for _, elt := range existing.Members {
+ interfaces = append(interfaces, elt.Name)
+ }
+
+ return interfaces, nil
+}
+
+type Layer3TemplateZoneImportLocation struct {
+ xpath []string
+ vsys string
+ zone string
+}
+
+type Layer3TemplateZoneImportLocationSpec struct {
+ Vsys string
+ Zone string
+}
+
+func NewLayer3TemplateZoneImportLocation(spec Layer3TemplateZoneImportLocationSpec) *Layer3TemplateImportLocation {
+ location := &Layer3TemplateZoneImportLocation{
+ vsys: spec.Vsys,
+ zone: spec.Zone,
+ }
+
+ return &Layer3TemplateImportLocation{
+ typ: layer3TemplateZone,
+ zone: location,
+ }
+}
+
+func (o *Layer3TemplateZoneImportLocation) XpathForLocation(vn version.Number, loc util.ILocation) ([]string, error) {
+ ans, err := loc.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ importAns := []string{
+ "vsys",
+ util.AsEntryXpath([]string{o.vsys}),
+ "zone",
+ util.AsEntryXpath([]string{o.zone}),
+ "network",
+ "layer3",
+ }
+
+ return append(ans, importAns...), nil
+}
+
+func (o *Layer3TemplateZoneImportLocation) MarshalPangoXML(interfaces []string) (string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type request struct {
+ XMLName xml.Name `xml:"layer3"`
+ Members []member `xml:"member"`
+ }
+
+ var members []member
+ for _, elt := range interfaces {
+ members = append(members, member{Name: elt})
+ }
+
+ expected := request{
+ Members: members,
+ }
+ bytes, err := xml.Marshal(expected)
+ if err != nil {
+ return "", err
+ }
+
+ return string(bytes), nil
+}
+
+func (o *Layer3TemplateZoneImportLocation) UnmarshalPangoXML(bytes []byte) ([]string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type response struct {
+ Members []member `xml:"result>layer3>member"`
+ }
+
+ var existing response
+ err := xml.Unmarshal(bytes, &existing)
+ if err != nil {
+ return nil, err
+ }
+
+ var interfaces []string
+ for _, elt := range existing.Members {
+ interfaces = append(interfaces, elt.Name)
+ }
+
+ return interfaces, nil
+}
+
+type Layer3TemplateVirtualRouterImportLocation struct {
+ xpath []string
+ router string
+ vsys string
+}
+
+type Layer3TemplateVirtualRouterImportLocationSpec struct {
+ Router string
+ Vsys string
+}
+
+func NewLayer3TemplateVirtualRouterImportLocation(spec Layer3TemplateVirtualRouterImportLocationSpec) *Layer3TemplateImportLocation {
+ location := &Layer3TemplateVirtualRouterImportLocation{
+ router: spec.Router,
+ vsys: spec.Vsys,
+ }
+
+ return &Layer3TemplateImportLocation{
+ typ: layer3TemplateVirtualRouter,
+ virtualRouter: location,
+ }
+}
+
+func (o *Layer3TemplateVirtualRouterImportLocation) XpathForLocation(vn version.Number, loc util.ILocation) ([]string, error) {
+ ans, err := loc.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ importAns := []string{
+ "network",
+ "virtual-router",
+ util.AsEntryXpath([]string{o.router}),
+ "interface",
+ }
+
+ return append(ans, importAns...), nil
+}
+
+func (o *Layer3TemplateVirtualRouterImportLocation) MarshalPangoXML(interfaces []string) (string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type request struct {
+ XMLName xml.Name `xml:"interface"`
+ Members []member `xml:"member"`
+ }
+
+ var members []member
+ for _, elt := range interfaces {
+ members = append(members, member{Name: elt})
+ }
+
+ expected := request{
+ Members: members,
+ }
+ bytes, err := xml.Marshal(expected)
+ if err != nil {
+ return "", err
+ }
+
+ return string(bytes), nil
+}
+
+func (o *Layer3TemplateVirtualRouterImportLocation) UnmarshalPangoXML(bytes []byte) ([]string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type response struct {
+ Members []member `xml:"result>interface>member"`
+ }
+
+ var existing response
+ err := xml.Unmarshal(bytes, &existing)
+ if err != nil {
+ return nil, err
+ }
+
+ var interfaces []string
+ for _, elt := range existing.Members {
+ interfaces = append(interfaces, elt.Name)
+ }
+
+ return interfaces, nil
+}
+
+type Layer3TemplateLogicalRouterImportLocation struct {
+ xpath []string
+ router string
+ vrf string
+ vsys string
+}
+
+type Layer3TemplateLogicalRouterImportLocationSpec struct {
+ Router string
+ Vrf string
+ Vsys string
+}
+
+func NewLayer3TemplateLogicalRouterImportLocation(spec Layer3TemplateLogicalRouterImportLocationSpec) *Layer3TemplateImportLocation {
+ location := &Layer3TemplateLogicalRouterImportLocation{
+ router: spec.Router,
+ vrf: spec.Vrf,
+ vsys: spec.Vsys,
+ }
+
+ return &Layer3TemplateImportLocation{
+ typ: layer3TemplateLogicalRouter,
+ logicalRouter: location,
+ }
+}
+
+func (o *Layer3TemplateLogicalRouterImportLocation) XpathForLocation(vn version.Number, loc util.ILocation) ([]string, error) {
+ ans, err := loc.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ importAns := []string{
+ "network",
+ "logical-router",
+ util.AsEntryXpath([]string{o.router}),
+ "vrf",
+ util.AsEntryXpath([]string{o.vrf}),
+ "interface",
+ }
+
+ return append(ans, importAns...), nil
+}
+
+func (o *Layer3TemplateLogicalRouterImportLocation) MarshalPangoXML(interfaces []string) (string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type request struct {
+ XMLName xml.Name `xml:"interface"`
+ Members []member `xml:"member"`
+ }
+
+ var members []member
+ for _, elt := range interfaces {
+ members = append(members, member{Name: elt})
+ }
+
+ expected := request{
+ Members: members,
+ }
+ bytes, err := xml.Marshal(expected)
+ if err != nil {
+ return "", err
+ }
+
+ return string(bytes), nil
+}
+
+func (o *Layer3TemplateLogicalRouterImportLocation) UnmarshalPangoXML(bytes []byte) ([]string, error) {
+ type member struct {
+ Name string `xml:",chardata"`
+ }
+
+ type response struct {
+ Members []member `xml:"result>interface>member"`
+ }
+
+ var existing response
+ err := xml.Unmarshal(bytes, &existing)
+ if err != nil {
+ return nil, err
+ }
+
+ var interfaces []string
+ for _, elt := range existing.Members {
+ interfaces = append(interfaces, elt.Name)
+ }
+
+ return interfaces, nil
+}
+
+func (o *Layer3TemplateImportLocation) MarshalPangoXML(interfaces []string) (string, error) {
+ switch o.typ {
+ case layer3TemplateVsys:
+ return o.vsys.MarshalPangoXML(interfaces)
+ case layer3TemplateZone:
+ return o.zone.MarshalPangoXML(interfaces)
+ case layer3TemplateVirtualRouter:
+ return o.virtualRouter.MarshalPangoXML(interfaces)
+ case layer3TemplateLogicalRouter:
+ return o.logicalRouter.MarshalPangoXML(interfaces)
+ default:
+ return "", fmt.Errorf("invalid import location")
+ }
+}
+
+func (o *Layer3TemplateImportLocation) UnmarshalPangoXML(bytes []byte) ([]string, error) {
+ switch o.typ {
+ case layer3TemplateVsys:
+ return o.vsys.UnmarshalPangoXML(bytes)
+ case layer3TemplateZone:
+ return o.zone.UnmarshalPangoXML(bytes)
+ case layer3TemplateVirtualRouter:
+ return o.virtualRouter.UnmarshalPangoXML(bytes)
+ case layer3TemplateLogicalRouter:
+ return o.logicalRouter.UnmarshalPangoXML(bytes)
+ default:
+ return nil, fmt.Errorf("invalid import location")
+ }
+}
+
+func (o *Layer3TemplateImportLocation) XpathForLocation(vn version.Number, loc util.ILocation) ([]string, error) {
+ switch o.typ {
+ case layer3TemplateVsys:
+ return o.vsys.XpathForLocation(vn, loc)
+ case layer3TemplateZone:
+ return o.zone.XpathForLocation(vn, loc)
+ case layer3TemplateVirtualRouter:
+ return o.virtualRouter.XpathForLocation(vn, loc)
+ case layer3TemplateLogicalRouter:
+ return o.logicalRouter.XpathForLocation(vn, loc)
+ default:
+ return nil, fmt.Errorf("invalid import location")
+ }
+}
+
+type Location struct {
+ Ngfw *NgfwLocation `json:"ngfw,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type NgfwLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewNgfwLocation() *Location {
+ return &Location{Ngfw: &NgfwLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Ngfw.NgfwDevice}),
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/network/interface/ethernet/service.go b/network/interface/ethernet/service.go
new file mode 100644
index 0000000..7ad26bb
--- /dev/null
+++ b/network/interface/ethernet/service.go
@@ -0,0 +1,388 @@
+package ethernet
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, importLocations []ImportLocation, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ err = s.importToLocations(ctx, loc, importLocations, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+func (s *Service) importToLocations(ctx context.Context, loc Location, importLocations []ImportLocation, entryName string) error {
+ vn := s.client.Versioning()
+ for _, elt := range importLocations {
+ xpath, err := elt.XpathForLocation(vn, loc)
+
+ cmd := &xmlapi.Config{
+ Action: "get",
+ Xpath: util.AsXpath(xpath),
+ }
+
+ bytes, _, err := s.client.Communicate(ctx, cmd, false, nil)
+ if err != nil && !errors.IsObjectNotFound(err) {
+ return err
+ }
+
+ existing, err := elt.UnmarshalPangoXML(bytes)
+ if err != nil {
+ return err
+ }
+
+ for _, elt := range existing {
+ if elt == entryName {
+ return nil
+ }
+ }
+
+ existing = append(existing, entryName)
+
+ element, err := elt.MarshalPangoXML(existing)
+ if err != nil {
+ return err
+ }
+
+ cmd = &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(xpath[:len(xpath)-1]),
+ Element: element,
+ }
+
+ _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *Service) unimportFromLocations(ctx context.Context, updates *xmlapi.MultiConfig, loc Location, importLocations []ImportLocation, values []string) error {
+ vn := s.client.Versioning()
+ valuesByName := make(map[string]bool)
+ for _, elt := range values {
+ valuesByName[elt] = true
+ }
+ for _, elt := range importLocations {
+ xpath, err := elt.XpathForLocation(vn, loc)
+
+ cmd := &xmlapi.Config{
+ Action: "get",
+ Xpath: util.AsXpath(xpath),
+ }
+
+ bytes, _, err := s.client.Communicate(ctx, cmd, false, nil)
+ if err != nil && !errors.IsObjectNotFound(err) {
+ return err
+ }
+
+ existing, err := elt.UnmarshalPangoXML(bytes)
+ if err != nil {
+ return err
+ }
+
+ var filtered []string
+ for _, elt := range existing {
+ if _, found := valuesByName[elt]; !found {
+ filtered = append(filtered, elt)
+ }
+ }
+
+ element, err := elt.MarshalPangoXML(filtered)
+ if err != nil {
+ return err
+ }
+
+ cmd = &xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(xpath),
+ Element: element,
+ }
+
+ _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, importLocations []ImportLocation, name ...string) error {
+ return s.delete(ctx, loc, importLocations, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, importLocations []ImportLocation, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ err = s.unimportFromLocations(ctx, deletes, loc, importLocations, values)
+ if err != nil {
+ return err
+ }
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/network/interface/loopback/entry.go b/network/interface/loopback/entry.go
new file mode 100644
index 0000000..2e24574
--- /dev/null
+++ b/network/interface/loopback/entry.go
@@ -0,0 +1,339 @@
+package loopback
+
+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{"network", "interface", "loopback", "units"}
+)
+
+type Entry struct {
+ Name string
+ AdjustTcpMss *AdjustTcpMss
+ Comment *string
+ InterfaceManagementProfile *string
+ Ips []string
+ Ipv6 *Ipv6
+ Mtu *int64
+ NetflowProfile *string
+
+ Misc map[string][]generic.Xml
+}
+
+type AdjustTcpMss struct {
+ Enable *bool
+ Ipv4MssAdjustment *int64
+ Ipv6MssAdjustment *int64
+}
+type Ipv6 struct {
+ Addresses []Ipv6Addresses
+ Enabled *bool
+}
+type Ipv6Addresses struct {
+ EnableOnInterface *bool
+ Name string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ AdjustTcpMss *AdjustTcpMssXml `xml:"adjust-tcp-mss,omitempty"`
+ Comment *string `xml:"comment,omitempty"`
+ InterfaceManagementProfile *string `xml:"interface-management-profile,omitempty"`
+ Ips *util.EntryType `xml:"ip,omitempty"`
+ Ipv6 *Ipv6Xml `xml:"ipv6,omitempty"`
+ Mtu *int64 `xml:"mtu,omitempty"`
+ NetflowProfile *string `xml:"netflow-profile,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type AdjustTcpMssXml struct {
+ Enable *string `xml:"enable,omitempty"`
+ Ipv4MssAdjustment *int64 `xml:"ipv4-mss-adjustment,omitempty"`
+ Ipv6MssAdjustment *int64 `xml:"ipv6-mss-adjustment,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Ipv6Xml struct {
+ Addresses []Ipv6AddressesXml `xml:"address>entry,omitempty"`
+ Enabled *string `xml:"enabled,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type Ipv6AddressesXml struct {
+ EnableOnInterface *string `xml:"enable-on-interface,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "adjust_tcp_mss" || v == "AdjustTcpMss" {
+ return e.AdjustTcpMss, nil
+ }
+ if v == "comment" || v == "Comment" {
+ return e.Comment, nil
+ }
+ if v == "interface_management_profile" || v == "InterfaceManagementProfile" {
+ return e.InterfaceManagementProfile, nil
+ }
+ if v == "ips" || v == "Ips" {
+ return e.Ips, nil
+ }
+ if v == "ips|LENGTH" || v == "Ips|LENGTH" {
+ return int64(len(e.Ips)), nil
+ }
+ if v == "ipv6" || v == "Ipv6" {
+ return e.Ipv6, nil
+ }
+ if v == "mtu" || v == "Mtu" {
+ return e.Mtu, nil
+ }
+ if v == "netflow_profile" || v == "NetflowProfile" {
+ return e.NetflowProfile, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ var nestedAdjustTcpMss *AdjustTcpMssXml
+ if o.AdjustTcpMss != nil {
+ nestedAdjustTcpMss = &AdjustTcpMssXml{}
+ if _, ok := o.Misc["AdjustTcpMss"]; ok {
+ nestedAdjustTcpMss.Misc = o.Misc["AdjustTcpMss"]
+ }
+ if o.AdjustTcpMss.Enable != nil {
+ nestedAdjustTcpMss.Enable = util.YesNo(o.AdjustTcpMss.Enable, nil)
+ }
+ if o.AdjustTcpMss.Ipv4MssAdjustment != nil {
+ nestedAdjustTcpMss.Ipv4MssAdjustment = o.AdjustTcpMss.Ipv4MssAdjustment
+ }
+ if o.AdjustTcpMss.Ipv6MssAdjustment != nil {
+ nestedAdjustTcpMss.Ipv6MssAdjustment = o.AdjustTcpMss.Ipv6MssAdjustment
+ }
+ }
+ entry.AdjustTcpMss = nestedAdjustTcpMss
+
+ entry.Comment = o.Comment
+ entry.InterfaceManagementProfile = o.InterfaceManagementProfile
+ entry.Ips = util.StrToEnt(o.Ips)
+ var nestedIpv6 *Ipv6Xml
+ if o.Ipv6 != nil {
+ nestedIpv6 = &Ipv6Xml{}
+ if _, ok := o.Misc["Ipv6"]; ok {
+ nestedIpv6.Misc = o.Misc["Ipv6"]
+ }
+ if o.Ipv6.Enabled != nil {
+ nestedIpv6.Enabled = util.YesNo(o.Ipv6.Enabled, nil)
+ }
+ if o.Ipv6.Addresses != nil {
+ nestedIpv6.Addresses = []Ipv6AddressesXml{}
+ for _, oIpv6Addresses := range o.Ipv6.Addresses {
+ nestedIpv6Addresses := Ipv6AddressesXml{}
+ if _, ok := o.Misc["Ipv6Addresses"]; ok {
+ nestedIpv6Addresses.Misc = o.Misc["Ipv6Addresses"]
+ }
+ if oIpv6Addresses.EnableOnInterface != nil {
+ nestedIpv6Addresses.EnableOnInterface = util.YesNo(oIpv6Addresses.EnableOnInterface, nil)
+ }
+ if oIpv6Addresses.Name != "" {
+ nestedIpv6Addresses.Name = oIpv6Addresses.Name
+ }
+ nestedIpv6.Addresses = append(nestedIpv6.Addresses, nestedIpv6Addresses)
+ }
+ }
+ }
+ entry.Ipv6 = nestedIpv6
+
+ entry.Mtu = o.Mtu
+ entry.NetflowProfile = o.NetflowProfile
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ var nestedAdjustTcpMss *AdjustTcpMss
+ if o.AdjustTcpMss != nil {
+ nestedAdjustTcpMss = &AdjustTcpMss{}
+ if o.AdjustTcpMss.Misc != nil {
+ entry.Misc["AdjustTcpMss"] = o.AdjustTcpMss.Misc
+ }
+ if o.AdjustTcpMss.Enable != nil {
+ nestedAdjustTcpMss.Enable = util.AsBool(o.AdjustTcpMss.Enable, nil)
+ }
+ if o.AdjustTcpMss.Ipv4MssAdjustment != nil {
+ nestedAdjustTcpMss.Ipv4MssAdjustment = o.AdjustTcpMss.Ipv4MssAdjustment
+ }
+ if o.AdjustTcpMss.Ipv6MssAdjustment != nil {
+ nestedAdjustTcpMss.Ipv6MssAdjustment = o.AdjustTcpMss.Ipv6MssAdjustment
+ }
+ }
+ entry.AdjustTcpMss = nestedAdjustTcpMss
+
+ entry.Comment = o.Comment
+ entry.InterfaceManagementProfile = o.InterfaceManagementProfile
+ entry.Ips = util.EntToStr(o.Ips)
+ var nestedIpv6 *Ipv6
+ if o.Ipv6 != nil {
+ nestedIpv6 = &Ipv6{}
+ if o.Ipv6.Misc != nil {
+ entry.Misc["Ipv6"] = o.Ipv6.Misc
+ }
+ if o.Ipv6.Enabled != nil {
+ nestedIpv6.Enabled = util.AsBool(o.Ipv6.Enabled, nil)
+ }
+ if o.Ipv6.Addresses != nil {
+ nestedIpv6.Addresses = []Ipv6Addresses{}
+ for _, oIpv6Addresses := range o.Ipv6.Addresses {
+ nestedIpv6Addresses := Ipv6Addresses{}
+ if oIpv6Addresses.Misc != nil {
+ entry.Misc["Ipv6Addresses"] = oIpv6Addresses.Misc
+ }
+ if oIpv6Addresses.EnableOnInterface != nil {
+ nestedIpv6Addresses.EnableOnInterface = util.AsBool(oIpv6Addresses.EnableOnInterface, nil)
+ }
+ if oIpv6Addresses.Name != "" {
+ nestedIpv6Addresses.Name = oIpv6Addresses.Name
+ }
+ nestedIpv6.Addresses = append(nestedIpv6.Addresses, nestedIpv6Addresses)
+ }
+ }
+ }
+ entry.Ipv6 = nestedIpv6
+
+ entry.Mtu = o.Mtu
+ entry.NetflowProfile = o.NetflowProfile
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchAdjustTcpMss(a.AdjustTcpMss, b.AdjustTcpMss) {
+ return false
+ }
+ if !util.StringsMatch(a.Comment, b.Comment) {
+ return false
+ }
+ if !util.StringsMatch(a.InterfaceManagementProfile, b.InterfaceManagementProfile) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Ips, b.Ips) {
+ return false
+ }
+ if !matchIpv6(a.Ipv6, b.Ipv6) {
+ return false
+ }
+ if !util.Ints64Match(a.Mtu, b.Mtu) {
+ return false
+ }
+ if !util.StringsMatch(a.NetflowProfile, b.NetflowProfile) {
+ return false
+ }
+
+ return true
+}
+
+func matchAdjustTcpMss(a *AdjustTcpMss, b *AdjustTcpMss) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.Ints64Match(a.Ipv4MssAdjustment, b.Ipv4MssAdjustment) {
+ return false
+ }
+ if !util.Ints64Match(a.Ipv6MssAdjustment, b.Ipv6MssAdjustment) {
+ return false
+ }
+ return true
+}
+func matchIpv6Addresses(a []Ipv6Addresses, b []Ipv6Addresses) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.BoolsMatch(a.EnableOnInterface, b.EnableOnInterface) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchIpv6(a *Ipv6, b *Ipv6) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enabled, b.Enabled) {
+ return false
+ }
+ if !matchIpv6Addresses(a.Addresses, b.Addresses) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/network/interface/loopback/interfaces.go b/network/interface/loopback/interfaces.go
new file mode 100644
index 0000000..96207a8
--- /dev/null
+++ b/network/interface/loopback/interfaces.go
@@ -0,0 +1,7 @@
+package loopback
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/network/interface/loopback/location.go b/network/interface/loopback/location.go
new file mode 100644
index 0000000..f0db0be
--- /dev/null
+++ b/network/interface/loopback/location.go
@@ -0,0 +1,187 @@
+package loopback
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Ngfw *NgfwLocation `json:"ngfw,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type NgfwLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewNgfwLocation() *Location {
+ return &Location{Ngfw: &NgfwLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Ngfw.NgfwDevice}),
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/network/interface/loopback/service.go b/network/interface/loopback/service.go
new file mode 100644
index 0000000..b088ca0
--- /dev/null
+++ b/network/interface/loopback/service.go
@@ -0,0 +1,281 @@
+package loopback
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/network/profiles/interface_management/entry.go b/network/profiles/interface_management/entry.go
new file mode 100644
index 0000000..e69baef
--- /dev/null
+++ b/network/profiles/interface_management/entry.go
@@ -0,0 +1,216 @@
+package interface_management
+
+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{"network", "profiles", "interface-management-profile"}
+)
+
+type Entry struct {
+ Name string
+ Http *bool
+ HttpOcsp *bool
+ Https *bool
+ PermittedIps []string
+ Ping *bool
+ ResponsePages *bool
+ Snmp *bool
+ Ssh *bool
+ Telnet *bool
+ UseridService *bool
+ UseridSyslogListenerSsl *bool
+ UseridSyslogListenerUdp *bool
+
+ Misc map[string][]generic.Xml
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Http *string `xml:"http,omitempty"`
+ HttpOcsp *string `xml:"http-ocsp,omitempty"`
+ Https *string `xml:"https,omitempty"`
+ PermittedIps *util.EntryType `xml:"permitted-ip,omitempty"`
+ Ping *string `xml:"ping,omitempty"`
+ ResponsePages *string `xml:"response-pages,omitempty"`
+ Snmp *string `xml:"snmp,omitempty"`
+ Ssh *string `xml:"ssh,omitempty"`
+ Telnet *string `xml:"telnet,omitempty"`
+ UseridService *string `xml:"userid-service,omitempty"`
+ UseridSyslogListenerSsl *string `xml:"userid-syslog-listener-ssl,omitempty"`
+ UseridSyslogListenerUdp *string `xml:"userid-syslog-listener-udp,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "http" || v == "Http" {
+ return e.Http, nil
+ }
+ if v == "http_ocsp" || v == "HttpOcsp" {
+ return e.HttpOcsp, nil
+ }
+ if v == "https" || v == "Https" {
+ return e.Https, nil
+ }
+ if v == "permitted_ips" || v == "PermittedIps" {
+ return e.PermittedIps, nil
+ }
+ if v == "permitted_ips|LENGTH" || v == "PermittedIps|LENGTH" {
+ return int64(len(e.PermittedIps)), nil
+ }
+ if v == "ping" || v == "Ping" {
+ return e.Ping, nil
+ }
+ if v == "response_pages" || v == "ResponsePages" {
+ return e.ResponsePages, nil
+ }
+ if v == "snmp" || v == "Snmp" {
+ return e.Snmp, nil
+ }
+ if v == "ssh" || v == "Ssh" {
+ return e.Ssh, nil
+ }
+ if v == "telnet" || v == "Telnet" {
+ return e.Telnet, nil
+ }
+ if v == "userid_service" || v == "UseridService" {
+ return e.UseridService, nil
+ }
+ if v == "userid_syslog_listener_ssl" || v == "UseridSyslogListenerSsl" {
+ return e.UseridSyslogListenerSsl, nil
+ }
+ if v == "userid_syslog_listener_udp" || v == "UseridSyslogListenerUdp" {
+ return e.UseridSyslogListenerUdp, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Http = util.YesNo(o.Http, nil)
+ entry.HttpOcsp = util.YesNo(o.HttpOcsp, nil)
+ entry.Https = util.YesNo(o.Https, nil)
+ entry.PermittedIps = util.StrToEnt(o.PermittedIps)
+ entry.Ping = util.YesNo(o.Ping, nil)
+ entry.ResponsePages = util.YesNo(o.ResponsePages, nil)
+ entry.Snmp = util.YesNo(o.Snmp, nil)
+ entry.Ssh = util.YesNo(o.Ssh, nil)
+ entry.Telnet = util.YesNo(o.Telnet, nil)
+ entry.UseridService = util.YesNo(o.UseridService, nil)
+ entry.UseridSyslogListenerSsl = util.YesNo(o.UseridSyslogListenerSsl, nil)
+ entry.UseridSyslogListenerUdp = util.YesNo(o.UseridSyslogListenerUdp, nil)
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Http = util.AsBool(o.Http, nil)
+ entry.HttpOcsp = util.AsBool(o.HttpOcsp, nil)
+ entry.Https = util.AsBool(o.Https, nil)
+ entry.PermittedIps = util.EntToStr(o.PermittedIps)
+ entry.Ping = util.AsBool(o.Ping, nil)
+ entry.ResponsePages = util.AsBool(o.ResponsePages, nil)
+ entry.Snmp = util.AsBool(o.Snmp, nil)
+ entry.Ssh = util.AsBool(o.Ssh, nil)
+ entry.Telnet = util.AsBool(o.Telnet, nil)
+ entry.UseridService = util.AsBool(o.UseridService, nil)
+ entry.UseridSyslogListenerSsl = util.AsBool(o.UseridSyslogListenerSsl, nil)
+ entry.UseridSyslogListenerUdp = util.AsBool(o.UseridSyslogListenerUdp, nil)
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.BoolsMatch(a.Http, b.Http) {
+ return false
+ }
+ if !util.BoolsMatch(a.HttpOcsp, b.HttpOcsp) {
+ return false
+ }
+ if !util.BoolsMatch(a.Https, b.Https) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.PermittedIps, b.PermittedIps) {
+ return false
+ }
+ if !util.BoolsMatch(a.Ping, b.Ping) {
+ return false
+ }
+ if !util.BoolsMatch(a.ResponsePages, b.ResponsePages) {
+ return false
+ }
+ if !util.BoolsMatch(a.Snmp, b.Snmp) {
+ return false
+ }
+ if !util.BoolsMatch(a.Ssh, b.Ssh) {
+ return false
+ }
+ if !util.BoolsMatch(a.Telnet, b.Telnet) {
+ return false
+ }
+ if !util.BoolsMatch(a.UseridService, b.UseridService) {
+ return false
+ }
+ if !util.BoolsMatch(a.UseridSyslogListenerSsl, b.UseridSyslogListenerSsl) {
+ return false
+ }
+ if !util.BoolsMatch(a.UseridSyslogListenerUdp, b.UseridSyslogListenerUdp) {
+ return false
+ }
+
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/network/profiles/interface_management/interfaces.go b/network/profiles/interface_management/interfaces.go
new file mode 100644
index 0000000..31a952f
--- /dev/null
+++ b/network/profiles/interface_management/interfaces.go
@@ -0,0 +1,7 @@
+package interface_management
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/network/profiles/interface_management/location.go b/network/profiles/interface_management/location.go
new file mode 100644
index 0000000..64ea8d9
--- /dev/null
+++ b/network/profiles/interface_management/location.go
@@ -0,0 +1,187 @@
+package interface_management
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Ngfw *NgfwLocation `json:"ngfw,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type NgfwLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewNgfwLocation() *Location {
+ return &Location{Ngfw: &NgfwLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Ngfw.NgfwDevice}),
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/network/profiles/interface_management/service.go b/network/profiles/interface_management/service.go
new file mode 100644
index 0000000..248eed1
--- /dev/null
+++ b/network/profiles/interface_management/service.go
@@ -0,0 +1,281 @@
+package interface_management
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/network/virtual_router/entry.go b/network/virtual_router/entry.go
new file mode 100644
index 0000000..664a03b
--- /dev/null
+++ b/network/virtual_router/entry.go
@@ -0,0 +1,1262 @@
+package virtual_router
+
+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{"network", "virtual-router"}
+)
+
+type Entry struct {
+ Name string
+ AdministrativeDistances *AdministrativeDistances
+ Ecmp *Ecmp
+ Interfaces []string
+ Protocol *Protocol
+ RoutingTable *RoutingTable
+
+ Misc map[string][]generic.Xml
+}
+
+type AdministrativeDistances struct {
+ Ebgp *int64
+ Ibgp *int64
+ OspfExt *int64
+ OspfInt *int64
+ Ospfv3Ext *int64
+ Ospfv3Int *int64
+ Rip *int64
+ Static *int64
+ StaticIpv6 *int64
+}
+type Ecmp struct {
+ Algorithm *EcmpAlgorithm
+ Enable *bool
+ MaxPaths *int64
+ StrictSourcePath *bool
+ SymmetricReturn *bool
+}
+type EcmpAlgorithm struct {
+ BalancedRoundRobin *EcmpAlgorithmBalancedRoundRobin
+ IpHash *EcmpAlgorithmIpHash
+ IpModulo *EcmpAlgorithmIpModulo
+ WeightedRoundRobin *EcmpAlgorithmWeightedRoundRobin
+}
+type EcmpAlgorithmBalancedRoundRobin struct {
+}
+type EcmpAlgorithmIpHash struct {
+ HashSeed *int64
+ SrcOnly *bool
+ UsePort *bool
+}
+type EcmpAlgorithmIpModulo struct {
+}
+type EcmpAlgorithmWeightedRoundRobin struct {
+ Interfaces []EcmpAlgorithmWeightedRoundRobinInterfaces
+}
+type EcmpAlgorithmWeightedRoundRobinInterfaces struct {
+ Name string
+ Weight *int64
+}
+type Protocol struct {
+ Bgp *ProtocolBgp
+ Ospf *ProtocolOspf
+ Ospfv3 *ProtocolOspfv3
+ Rip *ProtocolRip
+}
+type ProtocolBgp struct {
+ Enable *bool
+}
+type ProtocolOspf struct {
+ Enable *bool
+}
+type ProtocolOspfv3 struct {
+ Enable *bool
+}
+type ProtocolRip struct {
+ Enable *bool
+}
+type RoutingTable struct {
+ Ip *RoutingTableIp
+ Ipv6 *RoutingTableIpv6
+}
+type RoutingTableIp struct {
+ StaticRoutes []RoutingTableIpStaticRoutes
+}
+type RoutingTableIpStaticRoutes struct {
+ AdminDist *int64
+ Destination *string
+ Interface *string
+ Metric *int64
+ Name string
+ NextHop *RoutingTableIpStaticRoutesNextHop
+ RouteTable *string
+}
+type RoutingTableIpStaticRoutesNextHop struct {
+ Fqdn *string
+ IpAddress *string
+ NextVr *string
+ Tunnel *string
+}
+type RoutingTableIpv6 struct {
+ StaticRoutes []RoutingTableIpv6StaticRoutes
+}
+type RoutingTableIpv6StaticRoutes struct {
+ AdminDist *int64
+ Destination *string
+ Interface *string
+ Metric *int64
+ Name string
+ NextHop *RoutingTableIpv6StaticRoutesNextHop
+ RouteTable *string
+}
+type RoutingTableIpv6StaticRoutesNextHop struct {
+ Fqdn *string
+ Ipv6Address *string
+ NextVr *string
+ Tunnel *string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ AdministrativeDistances *AdministrativeDistancesXml `xml:"admin-dists,omitempty"`
+ Ecmp *EcmpXml `xml:"ecmp,omitempty"`
+ Interfaces *util.MemberType `xml:"interface,omitempty"`
+ Protocol *ProtocolXml `xml:"protocol,omitempty"`
+ RoutingTable *RoutingTableXml `xml:"routing-table,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type AdministrativeDistancesXml struct {
+ Ebgp *int64 `xml:"ebgp,omitempty"`
+ Ibgp *int64 `xml:"ibgp,omitempty"`
+ OspfExt *int64 `xml:"ospf-ext,omitempty"`
+ OspfInt *int64 `xml:"ospf-int,omitempty"`
+ Ospfv3Ext *int64 `xml:"ospfv3-ext,omitempty"`
+ Ospfv3Int *int64 `xml:"ospfv3-int,omitempty"`
+ Rip *int64 `xml:"rip,omitempty"`
+ Static *int64 `xml:"static,omitempty"`
+ StaticIpv6 *int64 `xml:"static-ipv6,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpXml struct {
+ Algorithm *EcmpAlgorithmXml `xml:"algorithm,omitempty"`
+ Enable *string `xml:"enable,omitempty"`
+ MaxPaths *int64 `xml:"max-path,omitempty"`
+ StrictSourcePath *string `xml:"strict-source-path,omitempty"`
+ SymmetricReturn *string `xml:"symmetric-return,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmXml struct {
+ BalancedRoundRobin *EcmpAlgorithmBalancedRoundRobinXml `xml:"balanced-round-robin,omitempty"`
+ IpHash *EcmpAlgorithmIpHashXml `xml:"ip-hash,omitempty"`
+ IpModulo *EcmpAlgorithmIpModuloXml `xml:"ip-modulo,omitempty"`
+ WeightedRoundRobin *EcmpAlgorithmWeightedRoundRobinXml `xml:"weighted-round-robin,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmBalancedRoundRobinXml struct {
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmIpHashXml struct {
+ HashSeed *int64 `xml:"hash-seed,omitempty"`
+ SrcOnly *string `xml:"src-only,omitempty"`
+ UsePort *string `xml:"use-port,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmIpModuloXml struct {
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmWeightedRoundRobinXml struct {
+ Interfaces []EcmpAlgorithmWeightedRoundRobinInterfacesXml `xml:"interface>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type EcmpAlgorithmWeightedRoundRobinInterfacesXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Weight *int64
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolXml struct {
+ Bgp *ProtocolBgpXml `xml:"bgp,omitempty"`
+ Ospf *ProtocolOspfXml `xml:"ospf,omitempty"`
+ Ospfv3 *ProtocolOspfv3Xml `xml:"ospfv3,omitempty"`
+ Rip *ProtocolRipXml `xml:"rip,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolBgpXml struct {
+ Enable *string `xml:"enable,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolOspfXml struct {
+ Enable *string `xml:"enable,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolOspfv3Xml struct {
+ Enable *string `xml:"enable,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolRipXml struct {
+ Enable *string `xml:"enable,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableXml struct {
+ Ip *RoutingTableIpXml `xml:"ip,omitempty"`
+ Ipv6 *RoutingTableIpv6Xml `xml:"ipv6,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpXml struct {
+ StaticRoutes []RoutingTableIpStaticRoutesXml `xml:"static-route>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpStaticRoutesXml struct {
+ AdminDist *int64 `xml:"admin-dist,omitempty"`
+ Destination *string `xml:"destination,omitempty"`
+ Interface *string `xml:"interface,omitempty"`
+ Metric *int64 `xml:"metric,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ NextHop *RoutingTableIpStaticRoutesNextHopXml `xml:"nexthop,omitempty"`
+ RouteTable *string `xml:"route-table,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpStaticRoutesNextHopXml struct {
+ Fqdn *string `xml:"fqdn,omitempty"`
+ IpAddress *string `xml:"ip-address,omitempty"`
+ NextVr *string `xml:"next-vr,omitempty"`
+ Tunnel *string `xml:"tunnel,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpv6Xml struct {
+ StaticRoutes []RoutingTableIpv6StaticRoutesXml `xml:"static-route>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpv6StaticRoutesXml struct {
+ AdminDist *int64 `xml:"admin-dist,omitempty"`
+ Destination *string `xml:"destination,omitempty"`
+ Interface *string `xml:"interface,omitempty"`
+ Metric *int64 `xml:"metric,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ NextHop *RoutingTableIpv6StaticRoutesNextHopXml `xml:"nexthop,omitempty"`
+ RouteTable *string `xml:"route-table,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type RoutingTableIpv6StaticRoutesNextHopXml struct {
+ Fqdn *string `xml:"fqdn,omitempty"`
+ Ipv6Address *string `xml:"ipv6-address,omitempty"`
+ NextVr *string `xml:"next-vr,omitempty"`
+ Tunnel *string `xml:"tunnel,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "administrative_distances" || v == "AdministrativeDistances" {
+ return e.AdministrativeDistances, nil
+ }
+ if v == "ecmp" || v == "Ecmp" {
+ return e.Ecmp, nil
+ }
+ if v == "interfaces" || v == "Interfaces" {
+ return e.Interfaces, nil
+ }
+ if v == "interfaces|LENGTH" || v == "Interfaces|LENGTH" {
+ return int64(len(e.Interfaces)), nil
+ }
+ if v == "protocol" || v == "Protocol" {
+ return e.Protocol, nil
+ }
+ if v == "routing_table" || v == "RoutingTable" {
+ return e.RoutingTable, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ var nestedAdministrativeDistances *AdministrativeDistancesXml
+ if o.AdministrativeDistances != nil {
+ nestedAdministrativeDistances = &AdministrativeDistancesXml{}
+ if _, ok := o.Misc["AdministrativeDistances"]; ok {
+ nestedAdministrativeDistances.Misc = o.Misc["AdministrativeDistances"]
+ }
+ if o.AdministrativeDistances.Ospfv3Int != nil {
+ nestedAdministrativeDistances.Ospfv3Int = o.AdministrativeDistances.Ospfv3Int
+ }
+ if o.AdministrativeDistances.Ebgp != nil {
+ nestedAdministrativeDistances.Ebgp = o.AdministrativeDistances.Ebgp
+ }
+ if o.AdministrativeDistances.Static != nil {
+ nestedAdministrativeDistances.Static = o.AdministrativeDistances.Static
+ }
+ if o.AdministrativeDistances.OspfInt != nil {
+ nestedAdministrativeDistances.OspfInt = o.AdministrativeDistances.OspfInt
+ }
+ if o.AdministrativeDistances.OspfExt != nil {
+ nestedAdministrativeDistances.OspfExt = o.AdministrativeDistances.OspfExt
+ }
+ if o.AdministrativeDistances.Rip != nil {
+ nestedAdministrativeDistances.Rip = o.AdministrativeDistances.Rip
+ }
+ if o.AdministrativeDistances.StaticIpv6 != nil {
+ nestedAdministrativeDistances.StaticIpv6 = o.AdministrativeDistances.StaticIpv6
+ }
+ if o.AdministrativeDistances.Ospfv3Ext != nil {
+ nestedAdministrativeDistances.Ospfv3Ext = o.AdministrativeDistances.Ospfv3Ext
+ }
+ if o.AdministrativeDistances.Ibgp != nil {
+ nestedAdministrativeDistances.Ibgp = o.AdministrativeDistances.Ibgp
+ }
+ }
+ entry.AdministrativeDistances = nestedAdministrativeDistances
+
+ var nestedEcmp *EcmpXml
+ if o.Ecmp != nil {
+ nestedEcmp = &EcmpXml{}
+ if _, ok := o.Misc["Ecmp"]; ok {
+ nestedEcmp.Misc = o.Misc["Ecmp"]
+ }
+ if o.Ecmp.SymmetricReturn != nil {
+ nestedEcmp.SymmetricReturn = util.YesNo(o.Ecmp.SymmetricReturn, nil)
+ }
+ if o.Ecmp.StrictSourcePath != nil {
+ nestedEcmp.StrictSourcePath = util.YesNo(o.Ecmp.StrictSourcePath, nil)
+ }
+ if o.Ecmp.MaxPaths != nil {
+ nestedEcmp.MaxPaths = o.Ecmp.MaxPaths
+ }
+ if o.Ecmp.Algorithm != nil {
+ nestedEcmp.Algorithm = &EcmpAlgorithmXml{}
+ if _, ok := o.Misc["EcmpAlgorithm"]; ok {
+ nestedEcmp.Algorithm.Misc = o.Misc["EcmpAlgorithm"]
+ }
+ if o.Ecmp.Algorithm.BalancedRoundRobin != nil {
+ nestedEcmp.Algorithm.BalancedRoundRobin = &EcmpAlgorithmBalancedRoundRobinXml{}
+ if _, ok := o.Misc["EcmpAlgorithmBalancedRoundRobin"]; ok {
+ nestedEcmp.Algorithm.BalancedRoundRobin.Misc = o.Misc["EcmpAlgorithmBalancedRoundRobin"]
+ }
+ }
+ if o.Ecmp.Algorithm.IpModulo != nil {
+ nestedEcmp.Algorithm.IpModulo = &EcmpAlgorithmIpModuloXml{}
+ if _, ok := o.Misc["EcmpAlgorithmIpModulo"]; ok {
+ nestedEcmp.Algorithm.IpModulo.Misc = o.Misc["EcmpAlgorithmIpModulo"]
+ }
+ }
+ if o.Ecmp.Algorithm.IpHash != nil {
+ nestedEcmp.Algorithm.IpHash = &EcmpAlgorithmIpHashXml{}
+ if _, ok := o.Misc["EcmpAlgorithmIpHash"]; ok {
+ nestedEcmp.Algorithm.IpHash.Misc = o.Misc["EcmpAlgorithmIpHash"]
+ }
+ if o.Ecmp.Algorithm.IpHash.SrcOnly != nil {
+ nestedEcmp.Algorithm.IpHash.SrcOnly = util.YesNo(o.Ecmp.Algorithm.IpHash.SrcOnly, nil)
+ }
+ if o.Ecmp.Algorithm.IpHash.UsePort != nil {
+ nestedEcmp.Algorithm.IpHash.UsePort = util.YesNo(o.Ecmp.Algorithm.IpHash.UsePort, nil)
+ }
+ if o.Ecmp.Algorithm.IpHash.HashSeed != nil {
+ nestedEcmp.Algorithm.IpHash.HashSeed = o.Ecmp.Algorithm.IpHash.HashSeed
+ }
+ }
+ if o.Ecmp.Algorithm.WeightedRoundRobin != nil {
+ nestedEcmp.Algorithm.WeightedRoundRobin = &EcmpAlgorithmWeightedRoundRobinXml{}
+ if _, ok := o.Misc["EcmpAlgorithmWeightedRoundRobin"]; ok {
+ nestedEcmp.Algorithm.WeightedRoundRobin.Misc = o.Misc["EcmpAlgorithmWeightedRoundRobin"]
+ }
+ if o.Ecmp.Algorithm.WeightedRoundRobin.Interfaces != nil {
+ nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces = []EcmpAlgorithmWeightedRoundRobinInterfacesXml{}
+ for _, oEcmpAlgorithmWeightedRoundRobinInterfaces := range o.Ecmp.Algorithm.WeightedRoundRobin.Interfaces {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces := EcmpAlgorithmWeightedRoundRobinInterfacesXml{}
+ if _, ok := o.Misc["EcmpAlgorithmWeightedRoundRobinInterfaces"]; ok {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces.Misc = o.Misc["EcmpAlgorithmWeightedRoundRobinInterfaces"]
+ }
+ if oEcmpAlgorithmWeightedRoundRobinInterfaces.Weight != nil {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces.Weight = oEcmpAlgorithmWeightedRoundRobinInterfaces.Weight
+ }
+ if oEcmpAlgorithmWeightedRoundRobinInterfaces.Name != "" {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces.Name = oEcmpAlgorithmWeightedRoundRobinInterfaces.Name
+ }
+ nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces = append(nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces, nestedEcmpAlgorithmWeightedRoundRobinInterfaces)
+ }
+ }
+ }
+ }
+ if o.Ecmp.Enable != nil {
+ nestedEcmp.Enable = util.YesNo(o.Ecmp.Enable, nil)
+ }
+ }
+ entry.Ecmp = nestedEcmp
+
+ entry.Interfaces = util.StrToMem(o.Interfaces)
+ var nestedProtocol *ProtocolXml
+ if o.Protocol != nil {
+ nestedProtocol = &ProtocolXml{}
+ if _, ok := o.Misc["Protocol"]; ok {
+ nestedProtocol.Misc = o.Misc["Protocol"]
+ }
+ if o.Protocol.Ospfv3 != nil {
+ nestedProtocol.Ospfv3 = &ProtocolOspfv3Xml{}
+ if _, ok := o.Misc["ProtocolOspfv3"]; ok {
+ nestedProtocol.Ospfv3.Misc = o.Misc["ProtocolOspfv3"]
+ }
+ if o.Protocol.Ospfv3.Enable != nil {
+ nestedProtocol.Ospfv3.Enable = util.YesNo(o.Protocol.Ospfv3.Enable, nil)
+ }
+ }
+ if o.Protocol.Bgp != nil {
+ nestedProtocol.Bgp = &ProtocolBgpXml{}
+ if _, ok := o.Misc["ProtocolBgp"]; ok {
+ nestedProtocol.Bgp.Misc = o.Misc["ProtocolBgp"]
+ }
+ if o.Protocol.Bgp.Enable != nil {
+ nestedProtocol.Bgp.Enable = util.YesNo(o.Protocol.Bgp.Enable, nil)
+ }
+ }
+ if o.Protocol.Rip != nil {
+ nestedProtocol.Rip = &ProtocolRipXml{}
+ if _, ok := o.Misc["ProtocolRip"]; ok {
+ nestedProtocol.Rip.Misc = o.Misc["ProtocolRip"]
+ }
+ if o.Protocol.Rip.Enable != nil {
+ nestedProtocol.Rip.Enable = util.YesNo(o.Protocol.Rip.Enable, nil)
+ }
+ }
+ if o.Protocol.Ospf != nil {
+ nestedProtocol.Ospf = &ProtocolOspfXml{}
+ if _, ok := o.Misc["ProtocolOspf"]; ok {
+ nestedProtocol.Ospf.Misc = o.Misc["ProtocolOspf"]
+ }
+ if o.Protocol.Ospf.Enable != nil {
+ nestedProtocol.Ospf.Enable = util.YesNo(o.Protocol.Ospf.Enable, nil)
+ }
+ }
+ }
+ entry.Protocol = nestedProtocol
+
+ var nestedRoutingTable *RoutingTableXml
+ if o.RoutingTable != nil {
+ nestedRoutingTable = &RoutingTableXml{}
+ if _, ok := o.Misc["RoutingTable"]; ok {
+ nestedRoutingTable.Misc = o.Misc["RoutingTable"]
+ }
+ if o.RoutingTable.Ip != nil {
+ nestedRoutingTable.Ip = &RoutingTableIpXml{}
+ if _, ok := o.Misc["RoutingTableIp"]; ok {
+ nestedRoutingTable.Ip.Misc = o.Misc["RoutingTableIp"]
+ }
+ if o.RoutingTable.Ip.StaticRoutes != nil {
+ nestedRoutingTable.Ip.StaticRoutes = []RoutingTableIpStaticRoutesXml{}
+ for _, oRoutingTableIpStaticRoutes := range o.RoutingTable.Ip.StaticRoutes {
+ nestedRoutingTableIpStaticRoutes := RoutingTableIpStaticRoutesXml{}
+ if _, ok := o.Misc["RoutingTableIpStaticRoutes"]; ok {
+ nestedRoutingTableIpStaticRoutes.Misc = o.Misc["RoutingTableIpStaticRoutes"]
+ }
+ if oRoutingTableIpStaticRoutes.NextHop != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop = &RoutingTableIpStaticRoutesNextHopXml{}
+ if _, ok := o.Misc["RoutingTableIpStaticRoutesNextHop"]; ok {
+ nestedRoutingTableIpStaticRoutes.NextHop.Misc = o.Misc["RoutingTableIpStaticRoutesNextHop"]
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.IpAddress != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.IpAddress = oRoutingTableIpStaticRoutes.NextHop.IpAddress
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.Fqdn != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.Fqdn = oRoutingTableIpStaticRoutes.NextHop.Fqdn
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.NextVr != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.NextVr = oRoutingTableIpStaticRoutes.NextHop.NextVr
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.Tunnel != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.Tunnel = oRoutingTableIpStaticRoutes.NextHop.Tunnel
+ }
+ }
+ if oRoutingTableIpStaticRoutes.AdminDist != nil {
+ nestedRoutingTableIpStaticRoutes.AdminDist = oRoutingTableIpStaticRoutes.AdminDist
+ }
+ if oRoutingTableIpStaticRoutes.Metric != nil {
+ nestedRoutingTableIpStaticRoutes.Metric = oRoutingTableIpStaticRoutes.Metric
+ }
+ if oRoutingTableIpStaticRoutes.RouteTable != nil {
+ nestedRoutingTableIpStaticRoutes.RouteTable = oRoutingTableIpStaticRoutes.RouteTable
+ }
+ if oRoutingTableIpStaticRoutes.Name != "" {
+ nestedRoutingTableIpStaticRoutes.Name = oRoutingTableIpStaticRoutes.Name
+ }
+ if oRoutingTableIpStaticRoutes.Destination != nil {
+ nestedRoutingTableIpStaticRoutes.Destination = oRoutingTableIpStaticRoutes.Destination
+ }
+ if oRoutingTableIpStaticRoutes.Interface != nil {
+ nestedRoutingTableIpStaticRoutes.Interface = oRoutingTableIpStaticRoutes.Interface
+ }
+ nestedRoutingTable.Ip.StaticRoutes = append(nestedRoutingTable.Ip.StaticRoutes, nestedRoutingTableIpStaticRoutes)
+ }
+ }
+ }
+ if o.RoutingTable.Ipv6 != nil {
+ nestedRoutingTable.Ipv6 = &RoutingTableIpv6Xml{}
+ if _, ok := o.Misc["RoutingTableIpv6"]; ok {
+ nestedRoutingTable.Ipv6.Misc = o.Misc["RoutingTableIpv6"]
+ }
+ if o.RoutingTable.Ipv6.StaticRoutes != nil {
+ nestedRoutingTable.Ipv6.StaticRoutes = []RoutingTableIpv6StaticRoutesXml{}
+ for _, oRoutingTableIpv6StaticRoutes := range o.RoutingTable.Ipv6.StaticRoutes {
+ nestedRoutingTableIpv6StaticRoutes := RoutingTableIpv6StaticRoutesXml{}
+ if _, ok := o.Misc["RoutingTableIpv6StaticRoutes"]; ok {
+ nestedRoutingTableIpv6StaticRoutes.Misc = o.Misc["RoutingTableIpv6StaticRoutes"]
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop = &RoutingTableIpv6StaticRoutesNextHopXml{}
+ if _, ok := o.Misc["RoutingTableIpv6StaticRoutesNextHop"]; ok {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Misc = o.Misc["RoutingTableIpv6StaticRoutesNextHop"]
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address = oRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Fqdn != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Fqdn = oRoutingTableIpv6StaticRoutes.NextHop.Fqdn
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.NextVr != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.NextVr = oRoutingTableIpv6StaticRoutes.NextHop.NextVr
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Tunnel != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Tunnel = oRoutingTableIpv6StaticRoutes.NextHop.Tunnel
+ }
+ }
+ if oRoutingTableIpv6StaticRoutes.AdminDist != nil {
+ nestedRoutingTableIpv6StaticRoutes.AdminDist = oRoutingTableIpv6StaticRoutes.AdminDist
+ }
+ if oRoutingTableIpv6StaticRoutes.Metric != nil {
+ nestedRoutingTableIpv6StaticRoutes.Metric = oRoutingTableIpv6StaticRoutes.Metric
+ }
+ if oRoutingTableIpv6StaticRoutes.RouteTable != nil {
+ nestedRoutingTableIpv6StaticRoutes.RouteTable = oRoutingTableIpv6StaticRoutes.RouteTable
+ }
+ if oRoutingTableIpv6StaticRoutes.Name != "" {
+ nestedRoutingTableIpv6StaticRoutes.Name = oRoutingTableIpv6StaticRoutes.Name
+ }
+ if oRoutingTableIpv6StaticRoutes.Destination != nil {
+ nestedRoutingTableIpv6StaticRoutes.Destination = oRoutingTableIpv6StaticRoutes.Destination
+ }
+ if oRoutingTableIpv6StaticRoutes.Interface != nil {
+ nestedRoutingTableIpv6StaticRoutes.Interface = oRoutingTableIpv6StaticRoutes.Interface
+ }
+ nestedRoutingTable.Ipv6.StaticRoutes = append(nestedRoutingTable.Ipv6.StaticRoutes, nestedRoutingTableIpv6StaticRoutes)
+ }
+ }
+ }
+ }
+ entry.RoutingTable = nestedRoutingTable
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ var nestedAdministrativeDistances *AdministrativeDistances
+ if o.AdministrativeDistances != nil {
+ nestedAdministrativeDistances = &AdministrativeDistances{}
+ if o.AdministrativeDistances.Misc != nil {
+ entry.Misc["AdministrativeDistances"] = o.AdministrativeDistances.Misc
+ }
+ if o.AdministrativeDistances.StaticIpv6 != nil {
+ nestedAdministrativeDistances.StaticIpv6 = o.AdministrativeDistances.StaticIpv6
+ }
+ if o.AdministrativeDistances.Ospfv3Ext != nil {
+ nestedAdministrativeDistances.Ospfv3Ext = o.AdministrativeDistances.Ospfv3Ext
+ }
+ if o.AdministrativeDistances.Ibgp != nil {
+ nestedAdministrativeDistances.Ibgp = o.AdministrativeDistances.Ibgp
+ }
+ if o.AdministrativeDistances.Rip != nil {
+ nestedAdministrativeDistances.Rip = o.AdministrativeDistances.Rip
+ }
+ if o.AdministrativeDistances.Ebgp != nil {
+ nestedAdministrativeDistances.Ebgp = o.AdministrativeDistances.Ebgp
+ }
+ if o.AdministrativeDistances.Static != nil {
+ nestedAdministrativeDistances.Static = o.AdministrativeDistances.Static
+ }
+ if o.AdministrativeDistances.OspfInt != nil {
+ nestedAdministrativeDistances.OspfInt = o.AdministrativeDistances.OspfInt
+ }
+ if o.AdministrativeDistances.OspfExt != nil {
+ nestedAdministrativeDistances.OspfExt = o.AdministrativeDistances.OspfExt
+ }
+ if o.AdministrativeDistances.Ospfv3Int != nil {
+ nestedAdministrativeDistances.Ospfv3Int = o.AdministrativeDistances.Ospfv3Int
+ }
+ }
+ entry.AdministrativeDistances = nestedAdministrativeDistances
+
+ var nestedEcmp *Ecmp
+ if o.Ecmp != nil {
+ nestedEcmp = &Ecmp{}
+ if o.Ecmp.Misc != nil {
+ entry.Misc["Ecmp"] = o.Ecmp.Misc
+ }
+ if o.Ecmp.Enable != nil {
+ nestedEcmp.Enable = util.AsBool(o.Ecmp.Enable, nil)
+ }
+ if o.Ecmp.SymmetricReturn != nil {
+ nestedEcmp.SymmetricReturn = util.AsBool(o.Ecmp.SymmetricReturn, nil)
+ }
+ if o.Ecmp.StrictSourcePath != nil {
+ nestedEcmp.StrictSourcePath = util.AsBool(o.Ecmp.StrictSourcePath, nil)
+ }
+ if o.Ecmp.MaxPaths != nil {
+ nestedEcmp.MaxPaths = o.Ecmp.MaxPaths
+ }
+ if o.Ecmp.Algorithm != nil {
+ nestedEcmp.Algorithm = &EcmpAlgorithm{}
+ if o.Ecmp.Algorithm.Misc != nil {
+ entry.Misc["EcmpAlgorithm"] = o.Ecmp.Algorithm.Misc
+ }
+ if o.Ecmp.Algorithm.IpModulo != nil {
+ nestedEcmp.Algorithm.IpModulo = &EcmpAlgorithmIpModulo{}
+ if o.Ecmp.Algorithm.IpModulo.Misc != nil {
+ entry.Misc["EcmpAlgorithmIpModulo"] = o.Ecmp.Algorithm.IpModulo.Misc
+ }
+ }
+ if o.Ecmp.Algorithm.IpHash != nil {
+ nestedEcmp.Algorithm.IpHash = &EcmpAlgorithmIpHash{}
+ if o.Ecmp.Algorithm.IpHash.Misc != nil {
+ entry.Misc["EcmpAlgorithmIpHash"] = o.Ecmp.Algorithm.IpHash.Misc
+ }
+ if o.Ecmp.Algorithm.IpHash.SrcOnly != nil {
+ nestedEcmp.Algorithm.IpHash.SrcOnly = util.AsBool(o.Ecmp.Algorithm.IpHash.SrcOnly, nil)
+ }
+ if o.Ecmp.Algorithm.IpHash.UsePort != nil {
+ nestedEcmp.Algorithm.IpHash.UsePort = util.AsBool(o.Ecmp.Algorithm.IpHash.UsePort, nil)
+ }
+ if o.Ecmp.Algorithm.IpHash.HashSeed != nil {
+ nestedEcmp.Algorithm.IpHash.HashSeed = o.Ecmp.Algorithm.IpHash.HashSeed
+ }
+ }
+ if o.Ecmp.Algorithm.WeightedRoundRobin != nil {
+ nestedEcmp.Algorithm.WeightedRoundRobin = &EcmpAlgorithmWeightedRoundRobin{}
+ if o.Ecmp.Algorithm.WeightedRoundRobin.Misc != nil {
+ entry.Misc["EcmpAlgorithmWeightedRoundRobin"] = o.Ecmp.Algorithm.WeightedRoundRobin.Misc
+ }
+ if o.Ecmp.Algorithm.WeightedRoundRobin.Interfaces != nil {
+ nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces = []EcmpAlgorithmWeightedRoundRobinInterfaces{}
+ for _, oEcmpAlgorithmWeightedRoundRobinInterfaces := range o.Ecmp.Algorithm.WeightedRoundRobin.Interfaces {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces := EcmpAlgorithmWeightedRoundRobinInterfaces{}
+ if oEcmpAlgorithmWeightedRoundRobinInterfaces.Misc != nil {
+ entry.Misc["EcmpAlgorithmWeightedRoundRobinInterfaces"] = oEcmpAlgorithmWeightedRoundRobinInterfaces.Misc
+ }
+ if oEcmpAlgorithmWeightedRoundRobinInterfaces.Weight != nil {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces.Weight = oEcmpAlgorithmWeightedRoundRobinInterfaces.Weight
+ }
+ if oEcmpAlgorithmWeightedRoundRobinInterfaces.Name != "" {
+ nestedEcmpAlgorithmWeightedRoundRobinInterfaces.Name = oEcmpAlgorithmWeightedRoundRobinInterfaces.Name
+ }
+ nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces = append(nestedEcmp.Algorithm.WeightedRoundRobin.Interfaces, nestedEcmpAlgorithmWeightedRoundRobinInterfaces)
+ }
+ }
+ }
+ if o.Ecmp.Algorithm.BalancedRoundRobin != nil {
+ nestedEcmp.Algorithm.BalancedRoundRobin = &EcmpAlgorithmBalancedRoundRobin{}
+ if o.Ecmp.Algorithm.BalancedRoundRobin.Misc != nil {
+ entry.Misc["EcmpAlgorithmBalancedRoundRobin"] = o.Ecmp.Algorithm.BalancedRoundRobin.Misc
+ }
+ }
+ }
+ }
+ entry.Ecmp = nestedEcmp
+
+ entry.Interfaces = util.MemToStr(o.Interfaces)
+ var nestedProtocol *Protocol
+ if o.Protocol != nil {
+ nestedProtocol = &Protocol{}
+ if o.Protocol.Misc != nil {
+ entry.Misc["Protocol"] = o.Protocol.Misc
+ }
+ if o.Protocol.Rip != nil {
+ nestedProtocol.Rip = &ProtocolRip{}
+ if o.Protocol.Rip.Misc != nil {
+ entry.Misc["ProtocolRip"] = o.Protocol.Rip.Misc
+ }
+ if o.Protocol.Rip.Enable != nil {
+ nestedProtocol.Rip.Enable = util.AsBool(o.Protocol.Rip.Enable, nil)
+ }
+ }
+ if o.Protocol.Ospf != nil {
+ nestedProtocol.Ospf = &ProtocolOspf{}
+ if o.Protocol.Ospf.Misc != nil {
+ entry.Misc["ProtocolOspf"] = o.Protocol.Ospf.Misc
+ }
+ if o.Protocol.Ospf.Enable != nil {
+ nestedProtocol.Ospf.Enable = util.AsBool(o.Protocol.Ospf.Enable, nil)
+ }
+ }
+ if o.Protocol.Ospfv3 != nil {
+ nestedProtocol.Ospfv3 = &ProtocolOspfv3{}
+ if o.Protocol.Ospfv3.Misc != nil {
+ entry.Misc["ProtocolOspfv3"] = o.Protocol.Ospfv3.Misc
+ }
+ if o.Protocol.Ospfv3.Enable != nil {
+ nestedProtocol.Ospfv3.Enable = util.AsBool(o.Protocol.Ospfv3.Enable, nil)
+ }
+ }
+ if o.Protocol.Bgp != nil {
+ nestedProtocol.Bgp = &ProtocolBgp{}
+ if o.Protocol.Bgp.Misc != nil {
+ entry.Misc["ProtocolBgp"] = o.Protocol.Bgp.Misc
+ }
+ if o.Protocol.Bgp.Enable != nil {
+ nestedProtocol.Bgp.Enable = util.AsBool(o.Protocol.Bgp.Enable, nil)
+ }
+ }
+ }
+ entry.Protocol = nestedProtocol
+
+ var nestedRoutingTable *RoutingTable
+ if o.RoutingTable != nil {
+ nestedRoutingTable = &RoutingTable{}
+ if o.RoutingTable.Misc != nil {
+ entry.Misc["RoutingTable"] = o.RoutingTable.Misc
+ }
+ if o.RoutingTable.Ip != nil {
+ nestedRoutingTable.Ip = &RoutingTableIp{}
+ if o.RoutingTable.Ip.Misc != nil {
+ entry.Misc["RoutingTableIp"] = o.RoutingTable.Ip.Misc
+ }
+ if o.RoutingTable.Ip.StaticRoutes != nil {
+ nestedRoutingTable.Ip.StaticRoutes = []RoutingTableIpStaticRoutes{}
+ for _, oRoutingTableIpStaticRoutes := range o.RoutingTable.Ip.StaticRoutes {
+ nestedRoutingTableIpStaticRoutes := RoutingTableIpStaticRoutes{}
+ if oRoutingTableIpStaticRoutes.Misc != nil {
+ entry.Misc["RoutingTableIpStaticRoutes"] = oRoutingTableIpStaticRoutes.Misc
+ }
+ if oRoutingTableIpStaticRoutes.Name != "" {
+ nestedRoutingTableIpStaticRoutes.Name = oRoutingTableIpStaticRoutes.Name
+ }
+ if oRoutingTableIpStaticRoutes.Destination != nil {
+ nestedRoutingTableIpStaticRoutes.Destination = oRoutingTableIpStaticRoutes.Destination
+ }
+ if oRoutingTableIpStaticRoutes.Interface != nil {
+ nestedRoutingTableIpStaticRoutes.Interface = oRoutingTableIpStaticRoutes.Interface
+ }
+ if oRoutingTableIpStaticRoutes.NextHop != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop = &RoutingTableIpStaticRoutesNextHop{}
+ if oRoutingTableIpStaticRoutes.NextHop.Misc != nil {
+ entry.Misc["RoutingTableIpStaticRoutesNextHop"] = oRoutingTableIpStaticRoutes.NextHop.Misc
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.Tunnel != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.Tunnel = oRoutingTableIpStaticRoutes.NextHop.Tunnel
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.IpAddress != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.IpAddress = oRoutingTableIpStaticRoutes.NextHop.IpAddress
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.Fqdn != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.Fqdn = oRoutingTableIpStaticRoutes.NextHop.Fqdn
+ }
+ if oRoutingTableIpStaticRoutes.NextHop.NextVr != nil {
+ nestedRoutingTableIpStaticRoutes.NextHop.NextVr = oRoutingTableIpStaticRoutes.NextHop.NextVr
+ }
+ }
+ if oRoutingTableIpStaticRoutes.AdminDist != nil {
+ nestedRoutingTableIpStaticRoutes.AdminDist = oRoutingTableIpStaticRoutes.AdminDist
+ }
+ if oRoutingTableIpStaticRoutes.Metric != nil {
+ nestedRoutingTableIpStaticRoutes.Metric = oRoutingTableIpStaticRoutes.Metric
+ }
+ if oRoutingTableIpStaticRoutes.RouteTable != nil {
+ nestedRoutingTableIpStaticRoutes.RouteTable = oRoutingTableIpStaticRoutes.RouteTable
+ }
+ nestedRoutingTable.Ip.StaticRoutes = append(nestedRoutingTable.Ip.StaticRoutes, nestedRoutingTableIpStaticRoutes)
+ }
+ }
+ }
+ if o.RoutingTable.Ipv6 != nil {
+ nestedRoutingTable.Ipv6 = &RoutingTableIpv6{}
+ if o.RoutingTable.Ipv6.Misc != nil {
+ entry.Misc["RoutingTableIpv6"] = o.RoutingTable.Ipv6.Misc
+ }
+ if o.RoutingTable.Ipv6.StaticRoutes != nil {
+ nestedRoutingTable.Ipv6.StaticRoutes = []RoutingTableIpv6StaticRoutes{}
+ for _, oRoutingTableIpv6StaticRoutes := range o.RoutingTable.Ipv6.StaticRoutes {
+ nestedRoutingTableIpv6StaticRoutes := RoutingTableIpv6StaticRoutes{}
+ if oRoutingTableIpv6StaticRoutes.Misc != nil {
+ entry.Misc["RoutingTableIpv6StaticRoutes"] = oRoutingTableIpv6StaticRoutes.Misc
+ }
+ if oRoutingTableIpv6StaticRoutes.RouteTable != nil {
+ nestedRoutingTableIpv6StaticRoutes.RouteTable = oRoutingTableIpv6StaticRoutes.RouteTable
+ }
+ if oRoutingTableIpv6StaticRoutes.Name != "" {
+ nestedRoutingTableIpv6StaticRoutes.Name = oRoutingTableIpv6StaticRoutes.Name
+ }
+ if oRoutingTableIpv6StaticRoutes.Destination != nil {
+ nestedRoutingTableIpv6StaticRoutes.Destination = oRoutingTableIpv6StaticRoutes.Destination
+ }
+ if oRoutingTableIpv6StaticRoutes.Interface != nil {
+ nestedRoutingTableIpv6StaticRoutes.Interface = oRoutingTableIpv6StaticRoutes.Interface
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop = &RoutingTableIpv6StaticRoutesNextHop{}
+ if oRoutingTableIpv6StaticRoutes.NextHop.Misc != nil {
+ entry.Misc["RoutingTableIpv6StaticRoutesNextHop"] = oRoutingTableIpv6StaticRoutes.NextHop.Misc
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address = oRoutingTableIpv6StaticRoutes.NextHop.Ipv6Address
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Fqdn != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Fqdn = oRoutingTableIpv6StaticRoutes.NextHop.Fqdn
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.NextVr != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.NextVr = oRoutingTableIpv6StaticRoutes.NextHop.NextVr
+ }
+ if oRoutingTableIpv6StaticRoutes.NextHop.Tunnel != nil {
+ nestedRoutingTableIpv6StaticRoutes.NextHop.Tunnel = oRoutingTableIpv6StaticRoutes.NextHop.Tunnel
+ }
+ }
+ if oRoutingTableIpv6StaticRoutes.AdminDist != nil {
+ nestedRoutingTableIpv6StaticRoutes.AdminDist = oRoutingTableIpv6StaticRoutes.AdminDist
+ }
+ if oRoutingTableIpv6StaticRoutes.Metric != nil {
+ nestedRoutingTableIpv6StaticRoutes.Metric = oRoutingTableIpv6StaticRoutes.Metric
+ }
+ nestedRoutingTable.Ipv6.StaticRoutes = append(nestedRoutingTable.Ipv6.StaticRoutes, nestedRoutingTableIpv6StaticRoutes)
+ }
+ }
+ }
+ }
+ entry.RoutingTable = nestedRoutingTable
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchAdministrativeDistances(a.AdministrativeDistances, b.AdministrativeDistances) {
+ return false
+ }
+ if !matchEcmp(a.Ecmp, b.Ecmp) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Interfaces, b.Interfaces) {
+ return false
+ }
+ if !matchProtocol(a.Protocol, b.Protocol) {
+ return false
+ }
+ if !matchRoutingTable(a.RoutingTable, b.RoutingTable) {
+ return false
+ }
+
+ return true
+}
+
+func matchRoutingTableIpStaticRoutesNextHop(a *RoutingTableIpStaticRoutesNextHop, b *RoutingTableIpStaticRoutesNextHop) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.Fqdn, b.Fqdn) {
+ return false
+ }
+ if !util.StringsMatch(a.NextVr, b.NextVr) {
+ return false
+ }
+ if !util.StringsMatch(a.Tunnel, b.Tunnel) {
+ return false
+ }
+ if !util.StringsMatch(a.IpAddress, b.IpAddress) {
+ return false
+ }
+ return true
+}
+func matchRoutingTableIpStaticRoutes(a []RoutingTableIpStaticRoutes, b []RoutingTableIpStaticRoutes) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.Ints64Match(a.Metric, b.Metric) {
+ return false
+ }
+ if !util.StringsMatch(a.RouteTable, b.RouteTable) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ if !util.StringsMatch(a.Destination, b.Destination) {
+ return false
+ }
+ if !util.StringsMatch(a.Interface, b.Interface) {
+ return false
+ }
+ if !matchRoutingTableIpStaticRoutesNextHop(a.NextHop, b.NextHop) {
+ return false
+ }
+ if !util.Ints64Match(a.AdminDist, b.AdminDist) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchRoutingTableIp(a *RoutingTableIp, b *RoutingTableIp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchRoutingTableIpStaticRoutes(a.StaticRoutes, b.StaticRoutes) {
+ return false
+ }
+ return true
+}
+func matchRoutingTableIpv6StaticRoutesNextHop(a *RoutingTableIpv6StaticRoutesNextHop, b *RoutingTableIpv6StaticRoutesNextHop) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.Fqdn, b.Fqdn) {
+ return false
+ }
+ if !util.StringsMatch(a.NextVr, b.NextVr) {
+ return false
+ }
+ if !util.StringsMatch(a.Tunnel, b.Tunnel) {
+ return false
+ }
+ if !util.StringsMatch(a.Ipv6Address, b.Ipv6Address) {
+ return false
+ }
+ return true
+}
+func matchRoutingTableIpv6StaticRoutes(a []RoutingTableIpv6StaticRoutes, b []RoutingTableIpv6StaticRoutes) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.StringsMatch(a.Destination, b.Destination) {
+ return false
+ }
+ if !util.StringsMatch(a.Interface, b.Interface) {
+ return false
+ }
+ if !matchRoutingTableIpv6StaticRoutesNextHop(a.NextHop, b.NextHop) {
+ return false
+ }
+ if !util.Ints64Match(a.AdminDist, b.AdminDist) {
+ return false
+ }
+ if !util.Ints64Match(a.Metric, b.Metric) {
+ return false
+ }
+ if !util.StringsMatch(a.RouteTable, b.RouteTable) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchRoutingTableIpv6(a *RoutingTableIpv6, b *RoutingTableIpv6) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchRoutingTableIpv6StaticRoutes(a.StaticRoutes, b.StaticRoutes) {
+ return false
+ }
+ return true
+}
+func matchRoutingTable(a *RoutingTable, b *RoutingTable) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchRoutingTableIp(a.Ip, b.Ip) {
+ return false
+ }
+ if !matchRoutingTableIpv6(a.Ipv6, b.Ipv6) {
+ return false
+ }
+ return true
+}
+func matchProtocolBgp(a *ProtocolBgp, b *ProtocolBgp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchProtocolRip(a *ProtocolRip, b *ProtocolRip) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchProtocolOspf(a *ProtocolOspf, b *ProtocolOspf) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchProtocolOspfv3(a *ProtocolOspfv3, b *ProtocolOspfv3) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ return true
+}
+func matchProtocol(a *Protocol, b *Protocol) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchProtocolBgp(a.Bgp, b.Bgp) {
+ return false
+ }
+ if !matchProtocolRip(a.Rip, b.Rip) {
+ return false
+ }
+ if !matchProtocolOspf(a.Ospf, b.Ospf) {
+ return false
+ }
+ if !matchProtocolOspfv3(a.Ospfv3, b.Ospfv3) {
+ return false
+ }
+ return true
+}
+func matchEcmpAlgorithmIpModulo(a *EcmpAlgorithmIpModulo, b *EcmpAlgorithmIpModulo) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ return true
+}
+func matchEcmpAlgorithmIpHash(a *EcmpAlgorithmIpHash, b *EcmpAlgorithmIpHash) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.SrcOnly, b.SrcOnly) {
+ return false
+ }
+ if !util.BoolsMatch(a.UsePort, b.UsePort) {
+ return false
+ }
+ if !util.Ints64Match(a.HashSeed, b.HashSeed) {
+ return false
+ }
+ return true
+}
+func matchEcmpAlgorithmWeightedRoundRobinInterfaces(a []EcmpAlgorithmWeightedRoundRobinInterfaces, b []EcmpAlgorithmWeightedRoundRobinInterfaces) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.Ints64Match(a.Weight, b.Weight) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchEcmpAlgorithmWeightedRoundRobin(a *EcmpAlgorithmWeightedRoundRobin, b *EcmpAlgorithmWeightedRoundRobin) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchEcmpAlgorithmWeightedRoundRobinInterfaces(a.Interfaces, b.Interfaces) {
+ return false
+ }
+ return true
+}
+func matchEcmpAlgorithmBalancedRoundRobin(a *EcmpAlgorithmBalancedRoundRobin, b *EcmpAlgorithmBalancedRoundRobin) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ return true
+}
+func matchEcmpAlgorithm(a *EcmpAlgorithm, b *EcmpAlgorithm) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchEcmpAlgorithmWeightedRoundRobin(a.WeightedRoundRobin, b.WeightedRoundRobin) {
+ return false
+ }
+ if !matchEcmpAlgorithmBalancedRoundRobin(a.BalancedRoundRobin, b.BalancedRoundRobin) {
+ return false
+ }
+ if !matchEcmpAlgorithmIpModulo(a.IpModulo, b.IpModulo) {
+ return false
+ }
+ if !matchEcmpAlgorithmIpHash(a.IpHash, b.IpHash) {
+ return false
+ }
+ return true
+}
+func matchEcmp(a *Ecmp, b *Ecmp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.Enable, b.Enable) {
+ return false
+ }
+ if !util.BoolsMatch(a.SymmetricReturn, b.SymmetricReturn) {
+ return false
+ }
+ if !util.BoolsMatch(a.StrictSourcePath, b.StrictSourcePath) {
+ return false
+ }
+ if !util.Ints64Match(a.MaxPaths, b.MaxPaths) {
+ return false
+ }
+ if !matchEcmpAlgorithm(a.Algorithm, b.Algorithm) {
+ return false
+ }
+ return true
+}
+func matchAdministrativeDistances(a *AdministrativeDistances, b *AdministrativeDistances) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.Static, b.Static) {
+ return false
+ }
+ if !util.Ints64Match(a.OspfInt, b.OspfInt) {
+ return false
+ }
+ if !util.Ints64Match(a.OspfExt, b.OspfExt) {
+ return false
+ }
+ if !util.Ints64Match(a.Ospfv3Int, b.Ospfv3Int) {
+ return false
+ }
+ if !util.Ints64Match(a.Ebgp, b.Ebgp) {
+ return false
+ }
+ if !util.Ints64Match(a.StaticIpv6, b.StaticIpv6) {
+ return false
+ }
+ if !util.Ints64Match(a.Ospfv3Ext, b.Ospfv3Ext) {
+ return false
+ }
+ if !util.Ints64Match(a.Ibgp, b.Ibgp) {
+ return false
+ }
+ if !util.Ints64Match(a.Rip, b.Rip) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/network/virtual_router/interfaces.go b/network/virtual_router/interfaces.go
new file mode 100644
index 0000000..c133d65
--- /dev/null
+++ b/network/virtual_router/interfaces.go
@@ -0,0 +1,7 @@
+package virtual_router
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/network/virtual_router/location.go b/network/virtual_router/location.go
new file mode 100644
index 0000000..613ea73
--- /dev/null
+++ b/network/virtual_router/location.go
@@ -0,0 +1,187 @@
+package virtual_router
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Ngfw *NgfwLocation `json:"ngfw,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+}
+
+type NgfwLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+}
+
+func NewNgfwLocation() *Location {
+ return &Location{Ngfw: &NgfwLocation{
+ NgfwDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Ngfw != nil:
+ if o.Ngfw.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Ngfw.NgfwDevice}),
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/network/virtual_router/service.go b/network/virtual_router/service.go
new file mode 100644
index 0000000..ef35a2f
--- /dev/null
+++ b/network/virtual_router/service.go
@@ -0,0 +1,281 @@
+package virtual_router
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/network/zone/entry.go b/network/zone/entry.go
new file mode 100644
index 0000000..b344d08
--- /dev/null
+++ b/network/zone/entry.go
@@ -0,0 +1,355 @@
+package zone
+
+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{"zone"}
+)
+
+type Entry struct {
+ Name string
+ DeviceAcl *DeviceAcl
+ EnableDeviceIdentification *bool
+ EnableUserIdentification *bool
+ Network *Network
+ UserAcl *UserAcl
+
+ Misc map[string][]generic.Xml
+}
+
+type DeviceAcl struct {
+ ExcludeList []string
+ IncludeList []string
+}
+type Network struct {
+ EnablePacketBufferProtection *bool
+ LogSetting []string
+ ZoneProtectionProfile []string
+ Layer2 []string
+ Layer3 []string
+ Tap []string
+ VirtualWire []string
+}
+type UserAcl struct {
+ ExcludeList []string
+ IncludeList []string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ DeviceAcl *DeviceAclXml `xml:"device-acl,omitempty"`
+ EnableDeviceIdentification *string `xml:"enable-device-identification,omitempty"`
+ EnableUserIdentification *string `xml:"enable-user-identification,omitempty"`
+ Network *NetworkXml `xml:"network,omitempty"`
+ UserAcl *UserAclXml `xml:"user-acl,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type DeviceAclXml struct {
+ ExcludeList *util.MemberType `xml:"exclude-list,omitempty"`
+ IncludeList *util.MemberType `xml:"include-list,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type NetworkXml struct {
+ EnablePacketBufferProtection *string `xml:"enable-packet-buffer-protection,omitempty"`
+ LogSetting *util.MemberType `xml:"log-setting,omitempty"`
+ ZoneProtectionProfile *util.MemberType `xml:"zone-protection-profile,omitempty"`
+ Layer2 *util.MemberType `xml:"layer2,omitempty"`
+ Layer3 *util.MemberType `xml:"layer3,omitempty"`
+ Tap *util.MemberType `xml:"tap,omitempty"`
+ VirtualWire *util.MemberType `xml:"virtual-wire,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type UserAclXml struct {
+ ExcludeList *util.MemberType `xml:"exclude-list,omitempty"`
+ IncludeList *util.MemberType `xml:"include-list,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "device_acl" || v == "DeviceAcl" {
+ return e.DeviceAcl, nil
+ }
+ if v == "enable_device_identification" || v == "EnableDeviceIdentification" {
+ return e.EnableDeviceIdentification, nil
+ }
+ if v == "enable_user_identification" || v == "EnableUserIdentification" {
+ return e.EnableUserIdentification, nil
+ }
+ if v == "network" || v == "Network" {
+ return e.Network, nil
+ }
+ if v == "user_acl" || v == "UserAcl" {
+ return e.UserAcl, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ var nestedDeviceAcl *DeviceAclXml
+ if o.DeviceAcl != nil {
+ nestedDeviceAcl = &DeviceAclXml{}
+ if _, ok := o.Misc["DeviceAcl"]; ok {
+ nestedDeviceAcl.Misc = o.Misc["DeviceAcl"]
+ }
+ if o.DeviceAcl.IncludeList != nil {
+ nestedDeviceAcl.IncludeList = util.StrToMem(o.DeviceAcl.IncludeList)
+ }
+ if o.DeviceAcl.ExcludeList != nil {
+ nestedDeviceAcl.ExcludeList = util.StrToMem(o.DeviceAcl.ExcludeList)
+ }
+ }
+ entry.DeviceAcl = nestedDeviceAcl
+
+ entry.EnableDeviceIdentification = util.YesNo(o.EnableDeviceIdentification, nil)
+ entry.EnableUserIdentification = util.YesNo(o.EnableUserIdentification, nil)
+ var nestedNetwork *NetworkXml
+ if o.Network != nil {
+ nestedNetwork = &NetworkXml{}
+ if _, ok := o.Misc["Network"]; ok {
+ nestedNetwork.Misc = o.Misc["Network"]
+ }
+ if o.Network.EnablePacketBufferProtection != nil {
+ nestedNetwork.EnablePacketBufferProtection = util.YesNo(o.Network.EnablePacketBufferProtection, nil)
+ }
+ if o.Network.ZoneProtectionProfile != nil {
+ nestedNetwork.ZoneProtectionProfile = util.StrToMem(o.Network.ZoneProtectionProfile)
+ }
+ if o.Network.LogSetting != nil {
+ nestedNetwork.LogSetting = util.StrToMem(o.Network.LogSetting)
+ }
+ if o.Network.Layer3 != nil {
+ nestedNetwork.Layer3 = util.StrToMem(o.Network.Layer3)
+ }
+ if o.Network.Layer2 != nil {
+ nestedNetwork.Layer2 = util.StrToMem(o.Network.Layer2)
+ }
+ if o.Network.VirtualWire != nil {
+ nestedNetwork.VirtualWire = util.StrToMem(o.Network.VirtualWire)
+ }
+ if o.Network.Tap != nil {
+ nestedNetwork.Tap = util.StrToMem(o.Network.Tap)
+ }
+ }
+ entry.Network = nestedNetwork
+
+ var nestedUserAcl *UserAclXml
+ if o.UserAcl != nil {
+ nestedUserAcl = &UserAclXml{}
+ if _, ok := o.Misc["UserAcl"]; ok {
+ nestedUserAcl.Misc = o.Misc["UserAcl"]
+ }
+ if o.UserAcl.IncludeList != nil {
+ nestedUserAcl.IncludeList = util.StrToMem(o.UserAcl.IncludeList)
+ }
+ if o.UserAcl.ExcludeList != nil {
+ nestedUserAcl.ExcludeList = util.StrToMem(o.UserAcl.ExcludeList)
+ }
+ }
+ entry.UserAcl = nestedUserAcl
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ var nestedDeviceAcl *DeviceAcl
+ if o.DeviceAcl != nil {
+ nestedDeviceAcl = &DeviceAcl{}
+ if o.DeviceAcl.Misc != nil {
+ entry.Misc["DeviceAcl"] = o.DeviceAcl.Misc
+ }
+ if o.DeviceAcl.IncludeList != nil {
+ nestedDeviceAcl.IncludeList = util.MemToStr(o.DeviceAcl.IncludeList)
+ }
+ if o.DeviceAcl.ExcludeList != nil {
+ nestedDeviceAcl.ExcludeList = util.MemToStr(o.DeviceAcl.ExcludeList)
+ }
+ }
+ entry.DeviceAcl = nestedDeviceAcl
+
+ entry.EnableDeviceIdentification = util.AsBool(o.EnableDeviceIdentification, nil)
+ entry.EnableUserIdentification = util.AsBool(o.EnableUserIdentification, nil)
+ var nestedNetwork *Network
+ if o.Network != nil {
+ nestedNetwork = &Network{}
+ if o.Network.Misc != nil {
+ entry.Misc["Network"] = o.Network.Misc
+ }
+ if o.Network.EnablePacketBufferProtection != nil {
+ nestedNetwork.EnablePacketBufferProtection = util.AsBool(o.Network.EnablePacketBufferProtection, nil)
+ }
+ if o.Network.ZoneProtectionProfile != nil {
+ nestedNetwork.ZoneProtectionProfile = util.MemToStr(o.Network.ZoneProtectionProfile)
+ }
+ if o.Network.LogSetting != nil {
+ nestedNetwork.LogSetting = util.MemToStr(o.Network.LogSetting)
+ }
+ if o.Network.Layer3 != nil {
+ nestedNetwork.Layer3 = util.MemToStr(o.Network.Layer3)
+ }
+ if o.Network.Layer2 != nil {
+ nestedNetwork.Layer2 = util.MemToStr(o.Network.Layer2)
+ }
+ if o.Network.VirtualWire != nil {
+ nestedNetwork.VirtualWire = util.MemToStr(o.Network.VirtualWire)
+ }
+ if o.Network.Tap != nil {
+ nestedNetwork.Tap = util.MemToStr(o.Network.Tap)
+ }
+ }
+ entry.Network = nestedNetwork
+
+ var nestedUserAcl *UserAcl
+ if o.UserAcl != nil {
+ nestedUserAcl = &UserAcl{}
+ if o.UserAcl.Misc != nil {
+ entry.Misc["UserAcl"] = o.UserAcl.Misc
+ }
+ if o.UserAcl.IncludeList != nil {
+ nestedUserAcl.IncludeList = util.MemToStr(o.UserAcl.IncludeList)
+ }
+ if o.UserAcl.ExcludeList != nil {
+ nestedUserAcl.ExcludeList = util.MemToStr(o.UserAcl.ExcludeList)
+ }
+ }
+ entry.UserAcl = nestedUserAcl
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchDeviceAcl(a.DeviceAcl, b.DeviceAcl) {
+ return false
+ }
+ if !util.BoolsMatch(a.EnableDeviceIdentification, b.EnableDeviceIdentification) {
+ return false
+ }
+ if !util.BoolsMatch(a.EnableUserIdentification, b.EnableUserIdentification) {
+ return false
+ }
+ if !matchNetwork(a.Network, b.Network) {
+ return false
+ }
+ if !matchUserAcl(a.UserAcl, b.UserAcl) {
+ return false
+ }
+
+ return true
+}
+
+func matchUserAcl(a *UserAcl, b *UserAcl) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.OrderedListsMatch(a.IncludeList, b.IncludeList) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.ExcludeList, b.ExcludeList) {
+ return false
+ }
+ return true
+}
+func matchDeviceAcl(a *DeviceAcl, b *DeviceAcl) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.OrderedListsMatch(a.IncludeList, b.IncludeList) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.ExcludeList, b.ExcludeList) {
+ return false
+ }
+ return true
+}
+func matchNetwork(a *Network, b *Network) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.BoolsMatch(a.EnablePacketBufferProtection, b.EnablePacketBufferProtection) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.ZoneProtectionProfile, b.ZoneProtectionProfile) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.LogSetting, b.LogSetting) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Tap, b.Tap) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Layer3, b.Layer3) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Layer2, b.Layer2) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.VirtualWire, b.VirtualWire) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/network/zone/interfaces.go b/network/zone/interfaces.go
new file mode 100644
index 0000000..8228a03
--- /dev/null
+++ b/network/zone/interfaces.go
@@ -0,0 +1,7 @@
+package zone
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/network/zone/location.go b/network/zone/location.go
new file mode 100644
index 0000000..3d1ca60
--- /dev/null
+++ b/network/zone/location.go
@@ -0,0 +1,243 @@
+package zone
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Template *TemplateLocation `json:"template,omitempty"`
+ TemplateStack *TemplateStackLocation `json:"template_stack,omitempty"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type TemplateLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+ Vsys string `json:"vsys"`
+}
+
+type TemplateStackLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ PanoramaDevice string `json:"panorama_device"`
+ TemplateStack string `json:"template_stack"`
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewTemplateStackLocation() *Location {
+ return &Location{TemplateStack: &TemplateStackLocation{
+ NgfwDevice: "localhost.localdomain",
+ PanoramaDevice: "localhost.localdomain",
+ TemplateStack: "",
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ if o.Template.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return fmt.Errorf("TemplateStack is unspecified")
+ }
+ if o.TemplateStack.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
+ case o.Template != nil:
+ if o.Template.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ if o.Template.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.NgfwDevice}),
+ "vsys",
+ util.AsEntryXpath([]string{o.Template.Vsys}),
+ }
+ case o.TemplateStack != nil:
+ if o.TemplateStack.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.TemplateStack.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.TemplateStack.TemplateStack == "" {
+ return nil, fmt.Errorf("TemplateStack is unspecified")
+ }
+ if o.TemplateStack.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.PanoramaDevice}),
+ "template-stack",
+ util.AsEntryXpath([]string{o.TemplateStack.TemplateStack}),
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.TemplateStack.NgfwDevice}),
+ "vsys",
+ util.AsEntryXpath([]string{o.TemplateStack.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}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/network/zone/service.go b/network/zone/service.go
new file mode 100644
index 0000000..2af5928
--- /dev/null
+++ b/network/zone/service.go
@@ -0,0 +1,281 @@
+package zone
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/objects/address/entry.go b/objects/address/entry.go
index d74ede2..9ac30da 100644
--- a/objects/address/entry.go
+++ b/objects/address/entry.go
@@ -21,24 +21,30 @@ var (
type Entry struct {
Name string
Description *string
- Tags []string // ordered
+ Tags []string
+ Fqdn *string
IpNetmask *string
IpRange *string
- Fqdn *string
- IpWildcard *string // PAN-OS 9.0
+ IpWildcard *string
Misc map[string][]generic.Xml
}
-func (e *Entry) CopyMiscFrom(v *Entry) {
- if v == nil || len(v.Misc) == 0 {
- return
- }
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
- e.Misc = make(map[string][]generic.Xml)
- for key := range v.Misc {
- e.Misc[key] = append([]generic.Xml(nil), v.Misc[key]...)
- }
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ Tags *util.MemberType `xml:"tag,omitempty"`
+ Fqdn *string `xml:"fqdn,omitempty"`
+ IpNetmask *string `xml:"ip-netmask,omitempty"`
+ IpRange *string `xml:"ip-range,omitempty"`
+ IpWildcard *string `xml:"ip-wildcard,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
}
func (e *Entry) Field(v string) (any, error) {
@@ -54,15 +60,15 @@ func (e *Entry) Field(v string) (any, error) {
if v == "tags|LENGTH" || v == "Tags|LENGTH" {
return int64(len(e.Tags)), nil
}
+ if v == "fqdn" || v == "Fqdn" {
+ return e.Fqdn, nil
+ }
if v == "ip_netmask" || v == "IpNetmask" {
return e.IpNetmask, nil
}
if v == "ip_range" || v == "IpRange" {
return e.IpRange, nil
}
- if v == "fqdn" || v == "Fqdn" {
- return e.Fqdn, nil
- }
if v == "ip_wildcard" || v == "IpWildcard" {
return e.IpWildcard, nil
}
@@ -71,60 +77,44 @@ func (e *Entry) Field(v string) (any, error) {
}
func Versioning(vn version.Number) (Specifier, Normalizer, error) {
- return Entry1Specify, &Entry1Container{}, nil
+ return specifyEntry, &entryXmlContainer{}, nil
}
-func Entry1Specify(o Entry) (any, error) {
- ans := Entry1{}
- ans.Name = o.Name
- ans.Description = o.Description
- ans.Tags = util.StrToMem(o.Tags)
- ans.IpNetmask = o.IpNetmask
- ans.IpRange = o.IpRange
- ans.Fqdn = o.Fqdn
- ans.IpWildcard = o.IpWildcard
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
- ans.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)]
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Tags = util.StrToMem(o.Tags)
+ entry.Fqdn = o.Fqdn
+ entry.IpNetmask = o.IpNetmask
+ entry.IpRange = o.IpRange
+ entry.IpWildcard = o.IpWildcard
- return ans, nil
-}
+ entry.Misc = o.Misc["Entry"]
-type Entry1Container struct {
- Answer []Entry1 `xml:"entry"`
+ return entry, nil
}
-
-func (c *Entry1Container) Normalize() ([]Entry, error) {
- ans := make([]Entry, 0, len(c.Answer))
- for _, var0 := range c.Answer {
- var1 := Entry{
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
Misc: make(map[string][]generic.Xml),
}
- var1.Name = var0.Name
- var1.Description = var0.Description
- var1.IpNetmask = var0.IpNetmask
- var1.IpRange = var0.IpRange
- var1.Fqdn = var0.Fqdn
- var1.IpWildcard = var0.IpWildcard
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Tags = util.MemToStr(o.Tags)
+ entry.Fqdn = o.Fqdn
+ entry.IpNetmask = o.IpNetmask
+ entry.IpRange = o.IpRange
+ entry.IpWildcard = o.IpWildcard
- var1.Misc[fmt.Sprintf("%s\n%s", "Entry", var0.Name)] = var0.Misc
+ entry.Misc["Entry"] = o.Misc
- ans = append(ans, var1)
+ entryList = append(entryList, entry)
}
- return ans, nil
-}
-
-type Entry1 struct {
- XMLName xml.Name `xml:"entry"`
- Name string `xml:"name,attr"`
- IpNetmask *string `xml:"ip-netmask"`
- IpRange *string `xml:"ip-range"`
- Fqdn *string `xml:"fqdn"`
- IpWildcard *string `xml:"ip-wildcard"`
- Description *string `xml:"description,omitempty"`
- Tags *util.MemberType `xml:"tag"`
-
- Misc []generic.Xml `xml:",any"`
+ return entryList, nil
}
func SpecMatches(a, b *Entry) bool {
@@ -135,30 +125,32 @@ func SpecMatches(a, b *Entry) bool {
}
// Don't compare Name.
-
- if !util.OptionalStringsMatch(a.Description, b.Description) {
+ if !util.StringsMatch(a.Description, b.Description) {
return false
}
-
if !util.OrderedListsMatch(a.Tags, b.Tags) {
return false
}
-
- if !util.OptionalStringsMatch(a.IpNetmask, b.IpNetmask) {
+ if !util.StringsMatch(a.Fqdn, b.Fqdn) {
return false
}
-
- if !util.OptionalStringsMatch(a.IpRange, b.IpRange) {
+ if !util.StringsMatch(a.IpNetmask, b.IpNetmask) {
return false
}
-
- if !util.OptionalStringsMatch(a.Fqdn, b.Fqdn) {
+ if !util.StringsMatch(a.IpRange, b.IpRange) {
return false
}
-
- if !util.OptionalStringsMatch(a.IpWildcard, b.IpWildcard) {
+ if !util.StringsMatch(a.IpWildcard, b.IpWildcard) {
return false
}
return true
}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/address/group/entry.go b/objects/address/group/entry.go
new file mode 100644
index 0000000..813f1df
--- /dev/null
+++ b/objects/address/group/entry.go
@@ -0,0 +1,136 @@
+package group
+
+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{"address-group"}
+)
+
+type Entry struct {
+ Name string
+ Description *string
+ Tags []string
+ Dynamic *string
+ Static []string
+
+ Misc map[string][]generic.Xml
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ Tags *util.MemberType `xml:"tag,omitempty"`
+ Dynamic *string `xml:"dynamic>filter,omitempty"`
+ Static *util.MemberType `xml:"static,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "tags" || v == "Tags" {
+ return e.Tags, nil
+ }
+ if v == "tags|LENGTH" || v == "Tags|LENGTH" {
+ return int64(len(e.Tags)), nil
+ }
+ if v == "dynamic" || v == "Dynamic" {
+ return e.Dynamic, nil
+ }
+ if v == "static" || v == "Static" {
+ return e.Static, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Tags = util.StrToMem(o.Tags)
+ entry.Dynamic = o.Dynamic
+ entry.Static = util.StrToMem(o.Static)
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Tags = util.MemToStr(o.Tags)
+ entry.Dynamic = o.Dynamic
+ entry.Static = util.MemToStr(o.Static)
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Tags, b.Tags) {
+ return false
+ }
+ if !util.StringsMatch(a.Dynamic, b.Dynamic) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Static, b.Static) {
+ return false
+ }
+
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/address/group/interfaces.go b/objects/address/group/interfaces.go
new file mode 100644
index 0000000..463f1be
--- /dev/null
+++ b/objects/address/group/interfaces.go
@@ -0,0 +1,7 @@
+package group
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/objects/address/group/location.go b/objects/address/group/location.go
new file mode 100644
index 0000000..204884e
--- /dev/null
+++ b/objects/address/group/location.go
@@ -0,0 +1,188 @@
+package group
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
+ case o.Shared:
+ ans = []string{
+ "config",
+ "shared",
+ }
+ 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}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/objects/address/group/service.go b/objects/address/group/service.go
new file mode 100644
index 0000000..ed586c7
--- /dev/null
+++ b/objects/address/group/service.go
@@ -0,0 +1,281 @@
+package group
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/objects/address/interfaces.go b/objects/address/interfaces.go
index 6cd929b..7abde91 100644
--- a/objects/address/interfaces.go
+++ b/objects/address/interfaces.go
@@ -1,7 +1,7 @@
package address
-type Specifier func(Entry) (any, error)
+type Specifier func(*Entry) (any, error)
type Normalizer interface {
- Normalize() ([]Entry, error)
+ Normalize() ([]*Entry, error)
}
diff --git a/objects/address/location.go b/objects/address/location.go
index b250abd..4c95d35 100644
--- a/objects/address/location.go
+++ b/objects/address/location.go
@@ -8,41 +8,88 @@ import (
"github.com/PaloAltoNetworks/pango/version"
)
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
type Location struct {
- Shared bool `json:"shared"`
- Vsys *VsysLocation `json:"vsys,omitempty"`
- DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
- FromPanorama bool `json:"from_panorama"`
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
}
-func (o Location) IsValid() error {
- count := 0
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
- if o.Shared {
- count++
- }
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
- if o.Vsys != nil {
- if o.Vsys.Name == "" {
- return fmt.Errorf("vsys.name is unspecified")
- }
- if o.Vsys.NgfwDevice == "" {
- return fmt.Errorf("vsys.ngfw_device is unspecified")
- }
- count++
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
}
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
- if o.DeviceGroup != nil {
- if o.DeviceGroup.Name == "" {
- return fmt.Errorf("device_group.name is unspecified")
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
}
if o.DeviceGroup.PanoramaDevice == "" {
- return fmt.Errorf("device_group.panorama_device is unspecified")
+ return fmt.Errorf("PanoramaDevice is unspecified")
}
count++
- }
-
- if o.FromPanorama {
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
count++
}
@@ -57,10 +104,41 @@ func (o Location) IsValid() error {
return nil
}
-func (o Location) Xpath(vn version.Number, name string) ([]string, error) {
+func (o Location) XpathPrefix(vn version.Number) ([]string, error) {
+
var ans []string
switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
case o.Shared:
ans = []string{
"config",
@@ -70,48 +148,41 @@ func (o Location) Xpath(vn version.Number, name string) ([]string, error) {
if o.Vsys.NgfwDevice == "" {
return nil, fmt.Errorf("NgfwDevice is unspecified")
}
- if o.Vsys.Name == "" {
- return nil, fmt.Errorf("Name 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.Name}),
- }
- case o.DeviceGroup != nil:
- if o.DeviceGroup.PanoramaDevice == "" {
- return nil, fmt.Errorf("PanoramaDevice is unspecified")
- }
- if o.DeviceGroup.Name == "" {
- return nil, fmt.Errorf("Name is unspecified")
+ util.AsEntryXpath([]string{o.Vsys.Vsys}),
}
- ans = []string{
- "config",
- "devices",
- util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
- "device-group",
- util.AsEntryXpath([]string{o.DeviceGroup.Name}),
- }
- case o.FromPanorama:
- ans = []string{"config", "panorama"}
default:
return nil, errors.NoLocationSpecifiedError
}
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
ans = append(ans, Suffix...)
ans = append(ans, util.AsEntryXpath([]string{name}))
return ans, nil
}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
-type VsysLocation struct {
- NgfwDevice string `json:"ngfw_device"`
- Name string `json:"name"`
-}
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
-type DeviceGroupLocation struct {
- PanoramaDevice string `json:"panorama_device"`
- Name string `json:"name"`
+ return ans, nil
}
diff --git a/objects/address/service.go b/objects/address/service.go
index 12a5115..23b0ea6 100644
--- a/objects/address/service.go
+++ b/objects/address/service.go
@@ -20,26 +20,22 @@ func NewService(client util.PangoClient) *Service {
}
}
-// Create creates the given config object.
-func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry, error) {
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
if entry.Name == "" {
return nil, errors.NameNotSpecifiedError
}
vn := s.client.Versioning()
- // Get versioning stuff.
specifier, _, err := Versioning(vn)
if err != nil {
return nil, err
}
-
- // Get the xpath.
- path, err := loc.Xpath(vn, entry.Name)
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
if err != nil {
return nil, err
}
-
createSpec, err := specifier(entry)
if err != nil {
return nil, err
@@ -52,93 +48,72 @@ func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry
Target: s.client.GetTarget(),
}
- // Perform the set.
if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
return nil, err
}
-
- // Return the Read results.
return s.Read(ctx, loc, entry.Name, "get")
}
-// Read returns the given config object, using the specified action.
-//
-// Param action should be either "get" or "show".
+// Read returns the given config object, using the specified action ("get" or "show").
func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
- if name == "" {
- return nil, errors.NameNotSpecifiedError
- }
-
- vn := s.client.Versioning()
- _, normalizer, err := Versioning(vn)
- if err != nil {
- return nil, err
- }
-
- path, err := loc.Xpath(vn, name)
- if err != nil {
- return nil, err
- }
-
- cmd := &xmlapi.Config{
- Action: action,
- Xpath: util.AsXpath(path),
- Target: s.client.GetTarget(),
- }
-
- if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
- // action=show returns empty config like this
- if err.Error() == "No such node" && action == "show" {
- return nil, errors.ObjectNotFound()
- }
- return nil, err
- }
-
- list, err := normalizer.Normalize()
- if err != nil {
- return nil, err
- } else if len(list) != 1 {
- return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
- }
-
- return &list[0], nil
+ return s.read(ctx, loc, name, action, false)
}
// ReadFromConfig returns the given config object from the loaded XML config.
-//
// Requires that client.LoadPanosConfig() has been invoked.
func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
- if name == "" {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
return nil, errors.NameNotSpecifiedError
}
-
vn := s.client.Versioning()
_, normalizer, err := Versioning(vn)
if err != nil {
return nil, err
}
-
- path, err := loc.Xpath(vn, name)
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
if err != nil {
return nil, err
}
- if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
- return nil, err
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
}
list, err := normalizer.Normalize()
if err != nil {
return nil, err
} else if len(list) != 1 {
- return nil, fmt.Errorf("expected to find 1 entry, got %d", len(list))
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
}
- return &list[0], nil
+ return list[0], nil
}
// Update updates the given config object, then returns the result.
-func (s *Service) Update(ctx context.Context, loc Location, entry Entry, oldName string) (*Entry, error) {
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
if entry.Name == "" {
return nil, errors.NameNotSpecifiedError
}
@@ -149,40 +124,35 @@ func (s *Service) Update(ctx context.Context, loc Location, entry Entry, oldName
if err != nil {
return nil, err
}
-
- // Get the old config.
var old *Entry
- if oldName != "" && oldName != entry.Name {
- // Action needed: rename.
- path, err := loc.Xpath(vn, oldName)
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
if err != nil {
return nil, err
}
- old, err = s.Read(ctx, loc, oldName, "get")
+ old, err = s.Read(ctx, loc, value, "get")
updates.Add(&xmlapi.Config{
Action: "rename",
Xpath: util.AsXpath(path),
NewName: entry.Name,
+ Target: s.client.GetTarget(),
})
} else {
old, err = s.Read(ctx, loc, entry.Name, "get")
}
if err != nil {
return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
}
-
- if !SpecMatches(&entry, old) {
- // Action needed: edit.
- path, err := loc.Xpath(vn, entry.Name)
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
if err != nil {
return nil, err
}
- // Copy over the misc stuff.
- entry.CopyMiscFrom(old)
-
updateSpec, err := specifier(entry)
if err != nil {
return nil, err
@@ -192,50 +162,64 @@ func (s *Service) Update(ctx context.Context, loc Location, entry Entry, oldName
Action: "edit",
Xpath: util.AsXpath(path),
Element: updateSpec,
+ Target: s.client.GetTarget(),
})
}
- // Do the updates we've built up.
if len(updates.Operations) != 0 {
if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
return nil, err
}
}
-
- // Return the read results.
return s.Read(ctx, loc, entry.Name, "get")
}
// Delete deletes the given item.
-func (s *Service) Delete(ctx context.Context, loc Location, name string) error {
- if name == "" {
- return errors.NameNotSpecifiedError
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
}
vn := s.client.Versioning()
-
- path, err := loc.Xpath(vn, name)
- if err != nil {
- return err
- }
-
- cmd := &xmlapi.Config{
- Action: "delete",
- Xpath: util.AsXpath(path),
- Target: s.client.GetTarget(),
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
}
- _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
return err
}
-// List returns a list of service objects using the given action.
-//
-// Param action should be either "get" or "show".
-//
+// List returns a list of objects using the given action ("get" or "show").
// Params filter and quote are for client side filtering.
-func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]Entry, error) {
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
var err error
var logic *filtering.Group
@@ -253,85 +237,38 @@ func (s *Service) List(ctx context.Context, loc Location, action, filter, quote
return nil, err
}
- path, err := loc.Xpath(vn, "")
+ path, err := loc.XpathWithEntryName(vn, "")
if err != nil {
return nil, err
}
- cmd := &xmlapi.Config{
- Action: action,
- Xpath: util.AsXpath(path),
- Target: s.client.GetTarget(),
- }
-
- if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
- // action=show returns empty config like this, it is not an error.
- if err.Error() == "No such node" && action == "show" {
- return nil, nil
- }
- return nil, err
- }
-
- listing, err := normalizer.Normalize()
- if err != nil || logic == nil {
- return listing, err
- }
-
- filtered := make([]Entry, 0, len(listing))
- for _, x := range listing {
- ok, err := logic.Matches(&x)
- if err != nil {
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
return nil, err
}
- if ok {
- filtered = append(filtered, x)
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
}
- }
-
- return filtered, nil
-}
-
-// ListFromConfig returns a list of objects at the given location.
-//
-// Requires that client.LoadPanosConfig() has been invoked.
-//
-// Params filter and quote are for client side filtering.
-func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]Entry, error) {
- var err error
- var logic *filtering.Group
- if filter != "" {
- logic, err = filtering.Parse(filter, quote)
- if err != nil {
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
return nil, err
}
}
- vn := s.client.Versioning()
-
- _, normalizer, err := Versioning(vn)
- if err != nil {
- return nil, err
- }
-
- path, err := loc.Xpath(vn, "")
- if err != nil {
- return nil, err
- }
- path = path[:len(path)-1]
-
- if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
- return nil, err
- }
-
listing, err := normalizer.Normalize()
if err != nil || logic == nil {
return listing, err
}
- filtered := make([]Entry, 0, len(listing))
+ filtered := make([]*Entry, 0, len(listing))
for _, x := range listing {
- ok, err := logic.Matches(&x)
+ ok, err := logic.Matches(x)
if err != nil {
return nil, err
}
@@ -342,123 +279,3 @@ func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quot
return filtered, nil
}
-
-// ConfigureGroup performs all necessary set / edit / delete commands to ensure that the
-// objects are configured as specified.
-func (s *Service) ConfigureGroup(ctx context.Context, loc Location, entries []Entry, prevNames []string) ([]Entry, error) {
- var err error
-
- vn := s.client.Versioning()
- updates := xmlapi.NewMultiConfig(len(prevNames) + len(entries))
- specifier, _, err := Versioning(vn)
- if err != nil {
- return nil, err
- }
-
- curObjs, err := s.List(ctx, loc, "get", "", "")
- if err != nil {
- return nil, err
- }
-
- //unfound := make([]Entry, 0, len(entries))
-
- // Determine set vs edit for desired objects.
- for _, entry := range entries {
- var found bool
- for _, live := range curObjs {
- if entry.Name == live.Name {
- found = true
- if !SpecMatches(&entry, &live) {
- path, err := loc.Xpath(vn, entry.Name)
- if err != nil {
- return nil, err
- }
-
- // Copy over the misc stuff.
- entry.CopyMiscFrom(&live)
-
- elm, err := specifier(entry)
- if err != nil {
- return nil, err
- }
-
- updates.Add(&xmlapi.Config{
- Action: "edit",
- Xpath: util.AsXpath(path),
- Element: elm,
- })
- }
- break
- }
- }
-
- if !found {
- path, err := loc.Xpath(vn, entry.Name)
- if err != nil {
- return nil, err
- }
-
- elm, err := specifier(entry)
- if err != nil {
- return nil, err
- }
-
- updates.Add(&xmlapi.Config{
- Action: "set",
- Xpath: util.AsXpath(path),
- Element: elm,
- })
- }
- }
-
- // Determine which old objects need to be removed.
- if len(prevNames) != 0 {
- for _, name := range prevNames {
- var found bool
- for _, entry := range entries {
- if entry.Name == name {
- found = true
- break
- }
- }
-
- if !found {
- path, err := loc.Xpath(vn, name)
- if err != nil {
- return nil, err
- }
-
- updates.Add(&xmlapi.Config{
- Action: "delete",
- Xpath: util.AsXpath(path),
- })
- }
- }
- }
-
- // Perform the multi-config if there was stuff to do.
- if len(updates.Operations) != 0 {
- if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
- return nil, err
- }
- }
-
- // Get the live version of the entries passed in.
- curObjs, err = s.List(ctx, loc, "get", "", "")
- if err != nil {
- return nil, err
- }
-
- ans := make([]Entry, 0, len(entries))
- for _, entry := range entries {
- for _, live := range curObjs {
- if entry.Name == live.Name {
- ans = append(ans, live)
- break
- }
- }
- }
-
- // Done.
- return ans, nil
-}
diff --git a/objects/profiles/entry.go b/objects/profiles/entry.go
new file mode 100644
index 0000000..fff04b5
--- /dev/null
+++ b/objects/profiles/entry.go
@@ -0,0 +1,136 @@
+package profiles
+
+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{"custom-url-category"}
+)
+
+type Entry struct {
+ Name string
+ Description *string
+ DisableOverride *bool
+ List []string
+ Type *string
+
+ Misc map[string][]generic.Xml
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ DisableOverride *string `xml:"disable-override,omitempty"`
+ List *util.MemberType `xml:"list,omitempty"`
+ Type *string `xml:"type,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "disable_override" || v == "DisableOverride" {
+ return e.DisableOverride, nil
+ }
+ if v == "list" || v == "List" {
+ return e.List, nil
+ }
+ if v == "list|LENGTH" || v == "List|LENGTH" {
+ return int64(len(e.List)), nil
+ }
+ if v == "type" || v == "Type" {
+ return e.Type, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.DisableOverride = util.YesNo(o.DisableOverride, nil)
+ entry.List = util.StrToMem(o.List)
+ entry.Type = o.Type
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.DisableOverride = util.AsBool(o.DisableOverride, nil)
+ entry.List = util.MemToStr(o.List)
+ entry.Type = o.Type
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !util.BoolsMatch(a.DisableOverride, b.DisableOverride) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.List, b.List) {
+ return false
+ }
+ if !util.StringsMatch(a.Type, b.Type) {
+ return false
+ }
+
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/profiles/interfaces.go b/objects/profiles/interfaces.go
new file mode 100644
index 0000000..f896f9b
--- /dev/null
+++ b/objects/profiles/interfaces.go
@@ -0,0 +1,7 @@
+package profiles
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/objects/profiles/location.go b/objects/profiles/location.go
new file mode 100644
index 0000000..0942a23
--- /dev/null
+++ b/objects/profiles/location.go
@@ -0,0 +1,193 @@
+package profiles
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ "profiles",
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ "profiles",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ "profiles",
+ }
+ case o.Shared:
+ ans = []string{
+ "config",
+ "shared",
+ "profiles",
+ }
+ 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}),
+ "profiles",
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/objects/profiles/service.go b/objects/profiles/service.go
new file mode 100644
index 0000000..90e5581
--- /dev/null
+++ b/objects/profiles/service.go
@@ -0,0 +1,281 @@
+package profiles
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/objects/service/entry.go b/objects/service/entry.go
new file mode 100644
index 0000000..ecdae2b
--- /dev/null
+++ b/objects/service/entry.go
@@ -0,0 +1,413 @@
+package service
+
+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{"service"}
+)
+
+type Entry struct {
+ Name string
+ Description *string
+ Protocol *Protocol
+ Tags []string
+
+ Misc map[string][]generic.Xml
+}
+
+type Protocol struct {
+ Tcp *ProtocolTcp
+ Udp *ProtocolUdp
+}
+type ProtocolTcp struct {
+ DestinationPort *int64
+ Override *ProtocolTcpOverride
+ SourcePort *int64
+}
+type ProtocolTcpOverride struct {
+ HalfcloseTimeout *int64
+ Timeout *int64
+ TimewaitTimeout *int64
+}
+type ProtocolUdp struct {
+ DestinationPort *int64
+ Override *ProtocolUdpOverride
+ SourcePort *int64
+}
+type ProtocolUdpOverride struct {
+ No *string
+ Yes *ProtocolUdpOverrideYes
+}
+type ProtocolUdpOverrideYes struct {
+ Timeout *int64
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ Protocol *ProtocolXml `xml:"protocol,omitempty"`
+ Tags *util.MemberType `xml:"tag,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type ProtocolXml struct {
+ Tcp *ProtocolTcpXml `xml:"tcp,omitempty"`
+ Udp *ProtocolUdpXml `xml:"udp,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolTcpXml struct {
+ DestinationPort *int64 `xml:"port,omitempty"`
+ Override *ProtocolTcpOverrideXml `xml:"override>yes,omitempty"`
+ SourcePort *int64 `xml:"source-port,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolTcpOverrideXml struct {
+ HalfcloseTimeout *int64 `xml:"halfclose-timeout,omitempty"`
+ Timeout *int64 `xml:"timeout,omitempty"`
+ TimewaitTimeout *int64 `xml:"timewait-timeout,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolUdpXml struct {
+ DestinationPort *int64 `xml:"port,omitempty"`
+ Override *ProtocolUdpOverrideXml `xml:"override,omitempty"`
+ SourcePort *int64 `xml:"source-port,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolUdpOverrideXml struct {
+ No *string `xml:"no,omitempty"`
+ Yes *ProtocolUdpOverrideYesXml `xml:"yes,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProtocolUdpOverrideYesXml struct {
+ Timeout *int64 `xml:"timeout,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "protocol" || v == "Protocol" {
+ return e.Protocol, nil
+ }
+ if v == "tags" || v == "Tags" {
+ return e.Tags, nil
+ }
+ if v == "tags|LENGTH" || v == "Tags|LENGTH" {
+ return int64(len(e.Tags)), nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Description = o.Description
+ var nestedProtocol *ProtocolXml
+ if o.Protocol != nil {
+ nestedProtocol = &ProtocolXml{}
+ if _, ok := o.Misc["Protocol"]; ok {
+ nestedProtocol.Misc = o.Misc["Protocol"]
+ }
+ if o.Protocol.Tcp != nil {
+ nestedProtocol.Tcp = &ProtocolTcpXml{}
+ if _, ok := o.Misc["ProtocolTcp"]; ok {
+ nestedProtocol.Tcp.Misc = o.Misc["ProtocolTcp"]
+ }
+ if o.Protocol.Tcp.DestinationPort != nil {
+ nestedProtocol.Tcp.DestinationPort = o.Protocol.Tcp.DestinationPort
+ }
+ if o.Protocol.Tcp.SourcePort != nil {
+ nestedProtocol.Tcp.SourcePort = o.Protocol.Tcp.SourcePort
+ }
+ if o.Protocol.Tcp.Override != nil {
+ nestedProtocol.Tcp.Override = &ProtocolTcpOverrideXml{}
+ if _, ok := o.Misc["ProtocolTcpOverride"]; ok {
+ nestedProtocol.Tcp.Override.Misc = o.Misc["ProtocolTcpOverride"]
+ }
+ if o.Protocol.Tcp.Override.TimewaitTimeout != nil {
+ nestedProtocol.Tcp.Override.TimewaitTimeout = o.Protocol.Tcp.Override.TimewaitTimeout
+ }
+ if o.Protocol.Tcp.Override.Timeout != nil {
+ nestedProtocol.Tcp.Override.Timeout = o.Protocol.Tcp.Override.Timeout
+ }
+ if o.Protocol.Tcp.Override.HalfcloseTimeout != nil {
+ nestedProtocol.Tcp.Override.HalfcloseTimeout = o.Protocol.Tcp.Override.HalfcloseTimeout
+ }
+ }
+ }
+ if o.Protocol.Udp != nil {
+ nestedProtocol.Udp = &ProtocolUdpXml{}
+ if _, ok := o.Misc["ProtocolUdp"]; ok {
+ nestedProtocol.Udp.Misc = o.Misc["ProtocolUdp"]
+ }
+ if o.Protocol.Udp.DestinationPort != nil {
+ nestedProtocol.Udp.DestinationPort = o.Protocol.Udp.DestinationPort
+ }
+ if o.Protocol.Udp.SourcePort != nil {
+ nestedProtocol.Udp.SourcePort = o.Protocol.Udp.SourcePort
+ }
+ if o.Protocol.Udp.Override != nil {
+ nestedProtocol.Udp.Override = &ProtocolUdpOverrideXml{}
+ if _, ok := o.Misc["ProtocolUdpOverride"]; ok {
+ nestedProtocol.Udp.Override.Misc = o.Misc["ProtocolUdpOverride"]
+ }
+ if o.Protocol.Udp.Override.Yes != nil {
+ nestedProtocol.Udp.Override.Yes = &ProtocolUdpOverrideYesXml{}
+ if _, ok := o.Misc["ProtocolUdpOverrideYes"]; ok {
+ nestedProtocol.Udp.Override.Yes.Misc = o.Misc["ProtocolUdpOverrideYes"]
+ }
+ if o.Protocol.Udp.Override.Yes.Timeout != nil {
+ nestedProtocol.Udp.Override.Yes.Timeout = o.Protocol.Udp.Override.Yes.Timeout
+ }
+ }
+ if o.Protocol.Udp.Override.No != nil {
+ nestedProtocol.Udp.Override.No = o.Protocol.Udp.Override.No
+ }
+ }
+ }
+ }
+ entry.Protocol = nestedProtocol
+
+ entry.Tags = util.StrToMem(o.Tags)
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Description = o.Description
+ var nestedProtocol *Protocol
+ if o.Protocol != nil {
+ nestedProtocol = &Protocol{}
+ if o.Protocol.Misc != nil {
+ entry.Misc["Protocol"] = o.Protocol.Misc
+ }
+ if o.Protocol.Udp != nil {
+ nestedProtocol.Udp = &ProtocolUdp{}
+ if o.Protocol.Udp.Misc != nil {
+ entry.Misc["ProtocolUdp"] = o.Protocol.Udp.Misc
+ }
+ if o.Protocol.Udp.DestinationPort != nil {
+ nestedProtocol.Udp.DestinationPort = o.Protocol.Udp.DestinationPort
+ }
+ if o.Protocol.Udp.SourcePort != nil {
+ nestedProtocol.Udp.SourcePort = o.Protocol.Udp.SourcePort
+ }
+ if o.Protocol.Udp.Override != nil {
+ nestedProtocol.Udp.Override = &ProtocolUdpOverride{}
+ if o.Protocol.Udp.Override.Misc != nil {
+ entry.Misc["ProtocolUdpOverride"] = o.Protocol.Udp.Override.Misc
+ }
+ if o.Protocol.Udp.Override.Yes != nil {
+ nestedProtocol.Udp.Override.Yes = &ProtocolUdpOverrideYes{}
+ if o.Protocol.Udp.Override.Yes.Misc != nil {
+ entry.Misc["ProtocolUdpOverrideYes"] = o.Protocol.Udp.Override.Yes.Misc
+ }
+ if o.Protocol.Udp.Override.Yes.Timeout != nil {
+ nestedProtocol.Udp.Override.Yes.Timeout = o.Protocol.Udp.Override.Yes.Timeout
+ }
+ }
+ if o.Protocol.Udp.Override.No != nil {
+ nestedProtocol.Udp.Override.No = o.Protocol.Udp.Override.No
+ }
+ }
+ }
+ if o.Protocol.Tcp != nil {
+ nestedProtocol.Tcp = &ProtocolTcp{}
+ if o.Protocol.Tcp.Misc != nil {
+ entry.Misc["ProtocolTcp"] = o.Protocol.Tcp.Misc
+ }
+ if o.Protocol.Tcp.DestinationPort != nil {
+ nestedProtocol.Tcp.DestinationPort = o.Protocol.Tcp.DestinationPort
+ }
+ if o.Protocol.Tcp.SourcePort != nil {
+ nestedProtocol.Tcp.SourcePort = o.Protocol.Tcp.SourcePort
+ }
+ if o.Protocol.Tcp.Override != nil {
+ nestedProtocol.Tcp.Override = &ProtocolTcpOverride{}
+ if o.Protocol.Tcp.Override.Misc != nil {
+ entry.Misc["ProtocolTcpOverride"] = o.Protocol.Tcp.Override.Misc
+ }
+ if o.Protocol.Tcp.Override.Timeout != nil {
+ nestedProtocol.Tcp.Override.Timeout = o.Protocol.Tcp.Override.Timeout
+ }
+ if o.Protocol.Tcp.Override.HalfcloseTimeout != nil {
+ nestedProtocol.Tcp.Override.HalfcloseTimeout = o.Protocol.Tcp.Override.HalfcloseTimeout
+ }
+ if o.Protocol.Tcp.Override.TimewaitTimeout != nil {
+ nestedProtocol.Tcp.Override.TimewaitTimeout = o.Protocol.Tcp.Override.TimewaitTimeout
+ }
+ }
+ }
+ }
+ entry.Protocol = nestedProtocol
+
+ entry.Tags = util.MemToStr(o.Tags)
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !matchProtocol(a.Protocol, b.Protocol) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Tags, b.Tags) {
+ return false
+ }
+
+ return true
+}
+
+func matchProtocolTcpOverride(a *ProtocolTcpOverride, b *ProtocolTcpOverride) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.HalfcloseTimeout, b.HalfcloseTimeout) {
+ return false
+ }
+ if !util.Ints64Match(a.TimewaitTimeout, b.TimewaitTimeout) {
+ return false
+ }
+ if !util.Ints64Match(a.Timeout, b.Timeout) {
+ return false
+ }
+ return true
+}
+func matchProtocolTcp(a *ProtocolTcp, b *ProtocolTcp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.DestinationPort, b.DestinationPort) {
+ return false
+ }
+ if !util.Ints64Match(a.SourcePort, b.SourcePort) {
+ return false
+ }
+ if !matchProtocolTcpOverride(a.Override, b.Override) {
+ return false
+ }
+ return true
+}
+func matchProtocolUdpOverrideYes(a *ProtocolUdpOverrideYes, b *ProtocolUdpOverrideYes) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.Timeout, b.Timeout) {
+ return false
+ }
+ return true
+}
+func matchProtocolUdpOverride(a *ProtocolUdpOverride, b *ProtocolUdpOverride) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.No, b.No) {
+ return false
+ }
+ if !matchProtocolUdpOverrideYes(a.Yes, b.Yes) {
+ return false
+ }
+ return true
+}
+func matchProtocolUdp(a *ProtocolUdp, b *ProtocolUdp) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.Ints64Match(a.SourcePort, b.SourcePort) {
+ return false
+ }
+ if !matchProtocolUdpOverride(a.Override, b.Override) {
+ return false
+ }
+ if !util.Ints64Match(a.DestinationPort, b.DestinationPort) {
+ return false
+ }
+ return true
+}
+func matchProtocol(a *Protocol, b *Protocol) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchProtocolTcp(a.Tcp, b.Tcp) {
+ return false
+ }
+ if !matchProtocolUdp(a.Udp, b.Udp) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/service/group/entry.go b/objects/service/group/entry.go
new file mode 100644
index 0000000..58d2faa
--- /dev/null
+++ b/objects/service/group/entry.go
@@ -0,0 +1,129 @@
+package group
+
+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{"service-group"}
+)
+
+type Entry struct {
+ Name string
+ Description *string
+ Members []string
+ Tags []string
+
+ Misc map[string][]generic.Xml
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ Members *util.MemberType `xml:"members,omitempty"`
+ Tags *util.MemberType `xml:"tag,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "members" || v == "Members" {
+ return e.Members, nil
+ }
+ if v == "members|LENGTH" || v == "Members|LENGTH" {
+ return int64(len(e.Members)), nil
+ }
+ if v == "tags" || v == "Tags" {
+ return e.Tags, nil
+ }
+ if v == "tags|LENGTH" || v == "Tags|LENGTH" {
+ return int64(len(e.Tags)), nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Members = util.StrToMem(o.Members)
+ entry.Tags = util.StrToMem(o.Tags)
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Description = o.Description
+ entry.Members = util.MemToStr(o.Members)
+ entry.Tags = util.MemToStr(o.Tags)
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Members, b.Members) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Tags, b.Tags) {
+ return false
+ }
+
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/service/group/interfaces.go b/objects/service/group/interfaces.go
new file mode 100644
index 0000000..463f1be
--- /dev/null
+++ b/objects/service/group/interfaces.go
@@ -0,0 +1,7 @@
+package group
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/objects/service/group/location.go b/objects/service/group/location.go
new file mode 100644
index 0000000..204884e
--- /dev/null
+++ b/objects/service/group/location.go
@@ -0,0 +1,188 @@
+package group
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
+ case o.Shared:
+ ans = []string{
+ "config",
+ "shared",
+ }
+ 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}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/objects/service/group/service.go b/objects/service/group/service.go
new file mode 100644
index 0000000..ed586c7
--- /dev/null
+++ b/objects/service/group/service.go
@@ -0,0 +1,281 @@
+package group
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/objects/service/interfaces.go b/objects/service/interfaces.go
new file mode 100644
index 0000000..7ae6bdc
--- /dev/null
+++ b/objects/service/interfaces.go
@@ -0,0 +1,7 @@
+package service
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/objects/service/location.go b/objects/service/location.go
new file mode 100644
index 0000000..6e29a18
--- /dev/null
+++ b/objects/service/location.go
@@ -0,0 +1,188 @@
+package service
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
+ case o.Shared:
+ ans = []string{
+ "config",
+ "shared",
+ }
+ 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}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/objects/service/service.go b/objects/service/service.go
new file mode 100644
index 0000000..42a6234
--- /dev/null
+++ b/objects/service/service.go
@@ -0,0 +1,281 @@
+package service
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/objects/tag/entry.go b/objects/tag/entry.go
new file mode 100644
index 0000000..51d888c
--- /dev/null
+++ b/objects/tag/entry.go
@@ -0,0 +1,156 @@
+package tag
+
+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{"tag"}
+)
+
+type Entry struct {
+ Name string
+ Color *string
+ Comments *string
+
+ Misc map[string][]generic.Xml
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Color *string `xml:"color,omitempty"`
+ Comments *string `xml:"comments,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+const (
+ ColorAzureBlue = "color24"
+ ColorBlack = "color14"
+ ColorBlueGray = "color12"
+ ColorBlueViolet = "color30"
+ ColorBrown = "color16"
+ ColorBurntSienna = "color41"
+ ColorCeruleanBlue = "color25"
+ ColorChestnut = "color42"
+ ColorCobaltBlue = "color28"
+ ColorCopper = "color4"
+ ColorCyan = "color9"
+ ColorForestGreen = "color22"
+ ColorGold = "color15"
+ ColorGray = "color7"
+ ColorGreen = "color2"
+ ColorLavender = "color33"
+ ColorLightGray = "color10"
+ ColorLightGreen = "color8"
+ ColorLime = "color13"
+ ColorMagenta = "color38"
+ ColorMahogany = "color40"
+ ColorMaroon = "color19"
+ ColorMediumBlue = "color27"
+ ColorMediumRose = "color32"
+ ColorMediumViolet = "color31"
+ ColorMidnightBlue = "color26"
+ ColorOlive = "color17"
+ ColorOrange = "color5"
+ ColorOrchid = "color34"
+ ColorPeach = "color36"
+ ColorPurple = "color6"
+ ColorRed = "color1"
+ ColorRedViolet = "color39"
+ ColorRedOrange = "color20"
+ ColorSalmon = "color37"
+ ColorThistle = "color35"
+ ColorTurquoiseBlue = "color23"
+ ColorVioletBlue = "color29"
+ ColorYellow = "color3"
+ ColorYellowOrange = "color21"
+)
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "color" || v == "Color" {
+ return e.Color, nil
+ }
+ if v == "comments" || v == "Comments" {
+ return e.Comments, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Color = o.Color
+ entry.Comments = o.Comments
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Color = o.Color
+ entry.Comments = o.Comments
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Color, b.Color) {
+ return false
+ }
+ if !util.StringsMatch(a.Comments, b.Comments) {
+ return false
+ }
+
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/objects/tag/interfaces.go b/objects/tag/interfaces.go
new file mode 100644
index 0000000..a4a515b
--- /dev/null
+++ b/objects/tag/interfaces.go
@@ -0,0 +1,7 @@
+package tag
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/objects/tag/location.go b/objects/tag/location.go
new file mode 100644
index 0000000..ac2ff82
--- /dev/null
+++ b/objects/tag/location.go
@@ -0,0 +1,188 @@
+package tag
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaShared bool `json:"from_panorama_shared"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared bool `json:"shared"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{
+ Shared: true,
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ case o.FromPanoramaShared:
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared:
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ }
+ case o.FromPanoramaShared:
+ ans = []string{
+ "config",
+ "panorama",
+ "shared",
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ }
+ case o.Shared:
+ ans = []string{
+ "config",
+ "shared",
+ }
+ 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}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/objects/tag/service.go b/objects/tag/service.go
new file mode 100644
index 0000000..1398eed
--- /dev/null
+++ b/objects/tag/service.go
@@ -0,0 +1,281 @@
+package tag
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/pango_suite_test.go b/pango_suite_test.go
new file mode 100644
index 0000000..2d9f1b0
--- /dev/null
+++ b/pango_suite_test.go
@@ -0,0 +1,15 @@
+package pango_test
+
+import (
+ "log/slog"
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestPango(t *testing.T) {
+ slog.SetDefault(slog.New(slog.NewTextHandler(GinkgoWriter, &slog.HandlerOptions{Level: slog.LevelDebug})))
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Pango Suite")
+}
diff --git a/panorama/device_group/entry.go b/panorama/device_group/entry.go
new file mode 100644
index 0000000..5bf9765
--- /dev/null
+++ b/panorama/device_group/entry.go
@@ -0,0 +1,207 @@
+package device_group
+
+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{"device-group"}
+)
+
+type Entry struct {
+ Name string
+ AuthorizationCode *string
+ Description *string
+ Devices []Devices
+ Templates []string
+
+ Misc map[string][]generic.Xml
+}
+
+type Devices struct {
+ Name string
+ Vsys []string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ AuthorizationCode *string `xml:"authorization-code,omitempty"`
+ Description *string `xml:"description,omitempty"`
+ Devices []DevicesXml `xml:"devices>entry,omitempty"`
+ Templates *util.MemberType `xml:"reference-templates,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type DevicesXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Vsys *util.MemberType `xml:"vsys,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "authorization_code" || v == "AuthorizationCode" {
+ return e.AuthorizationCode, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "devices" || v == "Devices" {
+ return e.Devices, nil
+ }
+ if v == "devices|LENGTH" || v == "Devices|LENGTH" {
+ return int64(len(e.Devices)), nil
+ }
+ if v == "templates" || v == "Templates" {
+ return e.Templates, nil
+ }
+ if v == "templates|LENGTH" || v == "Templates|LENGTH" {
+ return int64(len(e.Templates)), nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.AuthorizationCode = o.AuthorizationCode
+ entry.Description = o.Description
+ var nestedDevicesCol []DevicesXml
+ if o.Devices != nil {
+ nestedDevicesCol = []DevicesXml{}
+ for _, oDevices := range o.Devices {
+ nestedDevices := DevicesXml{}
+ if _, ok := o.Misc["Devices"]; ok {
+ nestedDevices.Misc = o.Misc["Devices"]
+ }
+ if oDevices.Vsys != nil {
+ nestedDevices.Vsys = util.StrToMem(oDevices.Vsys)
+ }
+ if oDevices.Name != "" {
+ nestedDevices.Name = oDevices.Name
+ }
+ nestedDevicesCol = append(nestedDevicesCol, nestedDevices)
+ }
+ entry.Devices = nestedDevicesCol
+ }
+
+ entry.Templates = util.StrToMem(o.Templates)
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.AuthorizationCode = o.AuthorizationCode
+ entry.Description = o.Description
+ var nestedDevicesCol []Devices
+ if o.Devices != nil {
+ nestedDevicesCol = []Devices{}
+ for _, oDevices := range o.Devices {
+ nestedDevices := Devices{}
+ if oDevices.Misc != nil {
+ entry.Misc["Devices"] = oDevices.Misc
+ }
+ if oDevices.Vsys != nil {
+ nestedDevices.Vsys = util.MemToStr(oDevices.Vsys)
+ }
+ if oDevices.Name != "" {
+ nestedDevices.Name = oDevices.Name
+ }
+ nestedDevicesCol = append(nestedDevicesCol, nestedDevices)
+ }
+ entry.Devices = nestedDevicesCol
+ }
+
+ entry.Templates = util.MemToStr(o.Templates)
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.AuthorizationCode, b.AuthorizationCode) {
+ return false
+ }
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !matchDevices(a.Devices, b.Devices) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Templates, b.Templates) {
+ return false
+ }
+
+ return true
+}
+
+func matchDevices(a []Devices, b []Devices) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !util.OrderedListsMatch(a.Vsys, b.Vsys) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/panorama/device_group/interfaces.go b/panorama/device_group/interfaces.go
new file mode 100644
index 0000000..c7693d4
--- /dev/null
+++ b/panorama/device_group/interfaces.go
@@ -0,0 +1,7 @@
+package device_group
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/panorama/device_group/location.go b/panorama/device_group/location.go
new file mode 100644
index 0000000..c17ea3b
--- /dev/null
+++ b/panorama/device_group/location.go
@@ -0,0 +1,95 @@
+package device_group
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Panorama *PanoramaLocation `json:"panorama,omitempty"`
+}
+
+type PanoramaLocation struct {
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+func NewPanoramaLocation() *Location {
+ return &Location{Panorama: &PanoramaLocation{
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Panorama.PanoramaDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/panorama/device_group/service.go b/panorama/device_group/service.go
new file mode 100644
index 0000000..aafca6a
--- /dev/null
+++ b/panorama/device_group/service.go
@@ -0,0 +1,281 @@
+package device_group
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/panorama/template/entry.go b/panorama/template/entry.go
new file mode 100644
index 0000000..836a4f5
--- /dev/null
+++ b/panorama/template/entry.go
@@ -0,0 +1,339 @@
+package template
+
+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{"template"}
+)
+
+type Entry struct {
+ Name string
+ Config *Config
+ DefaultVsys *string
+ Description *string
+
+ Misc map[string][]generic.Xml
+}
+
+type Config struct {
+ Devices []ConfigDevices
+}
+type ConfigDevices struct {
+ Name string
+ Vsys []ConfigDevicesVsys
+}
+type ConfigDevicesVsys struct {
+ Import *ConfigDevicesVsysImport
+ Name string
+}
+type ConfigDevicesVsysImport struct {
+ Network *ConfigDevicesVsysImportNetwork
+}
+type ConfigDevicesVsysImportNetwork struct {
+ Interfaces []string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Config *ConfigXml `xml:"config,omitempty"`
+ DefaultVsys *string `xml:"settings>default-vsys,omitempty"`
+ Description *string `xml:"description,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type ConfigXml struct {
+ Devices []ConfigDevicesXml `xml:"devices>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ConfigDevicesXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Vsys []ConfigDevicesVsysXml `xml:"vsys>entry,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ConfigDevicesVsysXml struct {
+ Import *ConfigDevicesVsysImportXml `xml:"import,omitempty"`
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ConfigDevicesVsysImportXml struct {
+ Network *ConfigDevicesVsysImportNetworkXml `xml:"network,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ConfigDevicesVsysImportNetworkXml struct {
+ Interfaces *util.MemberType `xml:"interface,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "config" || v == "Config" {
+ return e.Config, nil
+ }
+ if v == "default_vsys" || v == "DefaultVsys" {
+ return e.DefaultVsys, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ var nestedConfig *ConfigXml
+ if o.Config != nil {
+ nestedConfig = &ConfigXml{}
+ if _, ok := o.Misc["Config"]; ok {
+ nestedConfig.Misc = o.Misc["Config"]
+ }
+ if o.Config.Devices != nil {
+ nestedConfig.Devices = []ConfigDevicesXml{}
+ for _, oConfigDevices := range o.Config.Devices {
+ nestedConfigDevices := ConfigDevicesXml{}
+ if _, ok := o.Misc["ConfigDevices"]; ok {
+ nestedConfigDevices.Misc = o.Misc["ConfigDevices"]
+ }
+ if oConfigDevices.Vsys != nil {
+ nestedConfigDevices.Vsys = []ConfigDevicesVsysXml{}
+ for _, oConfigDevicesVsys := range oConfigDevices.Vsys {
+ nestedConfigDevicesVsys := ConfigDevicesVsysXml{}
+ if _, ok := o.Misc["ConfigDevicesVsys"]; ok {
+ nestedConfigDevicesVsys.Misc = o.Misc["ConfigDevicesVsys"]
+ }
+ if oConfigDevicesVsys.Import != nil {
+ nestedConfigDevicesVsys.Import = &ConfigDevicesVsysImportXml{}
+ if _, ok := o.Misc["ConfigDevicesVsysImport"]; ok {
+ nestedConfigDevicesVsys.Import.Misc = o.Misc["ConfigDevicesVsysImport"]
+ }
+ if oConfigDevicesVsys.Import.Network != nil {
+ nestedConfigDevicesVsys.Import.Network = &ConfigDevicesVsysImportNetworkXml{}
+ if _, ok := o.Misc["ConfigDevicesVsysImportNetwork"]; ok {
+ nestedConfigDevicesVsys.Import.Network.Misc = o.Misc["ConfigDevicesVsysImportNetwork"]
+ }
+ if oConfigDevicesVsys.Import.Network.Interfaces != nil {
+ nestedConfigDevicesVsys.Import.Network.Interfaces = util.StrToMem(oConfigDevicesVsys.Import.Network.Interfaces)
+ }
+ }
+ }
+ if oConfigDevicesVsys.Name != "" {
+ nestedConfigDevicesVsys.Name = oConfigDevicesVsys.Name
+ }
+ nestedConfigDevices.Vsys = append(nestedConfigDevices.Vsys, nestedConfigDevicesVsys)
+ }
+ }
+ if oConfigDevices.Name != "" {
+ nestedConfigDevices.Name = oConfigDevices.Name
+ }
+ nestedConfig.Devices = append(nestedConfig.Devices, nestedConfigDevices)
+ }
+ }
+ }
+ entry.Config = nestedConfig
+
+ entry.DefaultVsys = o.DefaultVsys
+ entry.Description = o.Description
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ var nestedConfig *Config
+ if o.Config != nil {
+ nestedConfig = &Config{}
+ if o.Config.Misc != nil {
+ entry.Misc["Config"] = o.Config.Misc
+ }
+ if o.Config.Devices != nil {
+ nestedConfig.Devices = []ConfigDevices{}
+ for _, oConfigDevices := range o.Config.Devices {
+ nestedConfigDevices := ConfigDevices{}
+ if oConfigDevices.Misc != nil {
+ entry.Misc["ConfigDevices"] = oConfigDevices.Misc
+ }
+ if oConfigDevices.Name != "" {
+ nestedConfigDevices.Name = oConfigDevices.Name
+ }
+ if oConfigDevices.Vsys != nil {
+ nestedConfigDevices.Vsys = []ConfigDevicesVsys{}
+ for _, oConfigDevicesVsys := range oConfigDevices.Vsys {
+ nestedConfigDevicesVsys := ConfigDevicesVsys{}
+ if oConfigDevicesVsys.Misc != nil {
+ entry.Misc["ConfigDevicesVsys"] = oConfigDevicesVsys.Misc
+ }
+ if oConfigDevicesVsys.Import != nil {
+ nestedConfigDevicesVsys.Import = &ConfigDevicesVsysImport{}
+ if oConfigDevicesVsys.Import.Misc != nil {
+ entry.Misc["ConfigDevicesVsysImport"] = oConfigDevicesVsys.Import.Misc
+ }
+ if oConfigDevicesVsys.Import.Network != nil {
+ nestedConfigDevicesVsys.Import.Network = &ConfigDevicesVsysImportNetwork{}
+ if oConfigDevicesVsys.Import.Network.Misc != nil {
+ entry.Misc["ConfigDevicesVsysImportNetwork"] = oConfigDevicesVsys.Import.Network.Misc
+ }
+ if oConfigDevicesVsys.Import.Network.Interfaces != nil {
+ nestedConfigDevicesVsys.Import.Network.Interfaces = util.MemToStr(oConfigDevicesVsys.Import.Network.Interfaces)
+ }
+ }
+ }
+ if oConfigDevicesVsys.Name != "" {
+ nestedConfigDevicesVsys.Name = oConfigDevicesVsys.Name
+ }
+ nestedConfigDevices.Vsys = append(nestedConfigDevices.Vsys, nestedConfigDevicesVsys)
+ }
+ }
+ nestedConfig.Devices = append(nestedConfig.Devices, nestedConfigDevices)
+ }
+ }
+ }
+ entry.Config = nestedConfig
+
+ entry.DefaultVsys = o.DefaultVsys
+ entry.Description = o.Description
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !matchConfig(a.Config, b.Config) {
+ return false
+ }
+ if !util.StringsMatch(a.DefaultVsys, b.DefaultVsys) {
+ return false
+ }
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+
+ return true
+}
+
+func matchConfigDevicesVsysImportNetwork(a *ConfigDevicesVsysImportNetwork, b *ConfigDevicesVsysImportNetwork) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.OrderedListsMatch(a.Interfaces, b.Interfaces) {
+ return false
+ }
+ return true
+}
+func matchConfigDevicesVsysImport(a *ConfigDevicesVsysImport, b *ConfigDevicesVsysImport) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchConfigDevicesVsysImportNetwork(a.Network, b.Network) {
+ return false
+ }
+ return true
+}
+func matchConfigDevicesVsys(a []ConfigDevicesVsys, b []ConfigDevicesVsys) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !matchConfigDevicesVsysImport(a.Import, b.Import) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchConfigDevices(a []ConfigDevices, b []ConfigDevices) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ for _, a := range a {
+ for _, b := range b {
+ if !matchConfigDevicesVsys(a.Vsys, b.Vsys) {
+ return false
+ }
+ if !util.StringsEqual(a.Name, b.Name) {
+ return false
+ }
+ }
+ }
+ return true
+}
+func matchConfig(a *Config, b *Config) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !matchConfigDevices(a.Devices, b.Devices) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/panorama/template/interfaces.go b/panorama/template/interfaces.go
new file mode 100644
index 0000000..df09955
--- /dev/null
+++ b/panorama/template/interfaces.go
@@ -0,0 +1,7 @@
+package template
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/panorama/template/location.go b/panorama/template/location.go
new file mode 100644
index 0000000..27cdd62
--- /dev/null
+++ b/panorama/template/location.go
@@ -0,0 +1,95 @@
+package template
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Panorama *PanoramaLocation `json:"panorama,omitempty"`
+}
+
+type PanoramaLocation struct {
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+func NewPanoramaLocation() *Location {
+ return &Location{Panorama: &PanoramaLocation{
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Panorama.PanoramaDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/panorama/template/service.go b/panorama/template/service.go
new file mode 100644
index 0000000..646ebc5
--- /dev/null
+++ b/panorama/template/service.go
@@ -0,0 +1,281 @@
+package template
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/panorama/template_stack/entry.go b/panorama/template_stack/entry.go
new file mode 100644
index 0000000..e5a38e4
--- /dev/null
+++ b/panorama/template_stack/entry.go
@@ -0,0 +1,191 @@
+package template_stack
+
+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{"template-stack"}
+)
+
+type Entry struct {
+ Name string
+ DefaultVsys *string
+ Description *string
+ Devices []string
+ Templates []string
+ UserGroupSource *UserGroupSource
+
+ Misc map[string][]generic.Xml
+}
+
+type UserGroupSource struct {
+ MasterDevice *string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ DefaultVsys *string `xml:"settings>default-vsys,omitempty"`
+ Description *string `xml:"description,omitempty"`
+ Devices *util.EntryType `xml:"devices,omitempty"`
+ Templates *util.MemberType `xml:"templates,omitempty"`
+ UserGroupSource *UserGroupSourceXml `xml:"user-group-source,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type UserGroupSourceXml struct {
+ MasterDevice *string `xml:"master-device,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "default_vsys" || v == "DefaultVsys" {
+ return e.DefaultVsys, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "devices" || v == "Devices" {
+ return e.Devices, nil
+ }
+ if v == "devices|LENGTH" || v == "Devices|LENGTH" {
+ return int64(len(e.Devices)), nil
+ }
+ if v == "templates" || v == "Templates" {
+ return e.Templates, nil
+ }
+ if v == "templates|LENGTH" || v == "Templates|LENGTH" {
+ return int64(len(e.Templates)), nil
+ }
+ if v == "user_group_source" || v == "UserGroupSource" {
+ return e.UserGroupSource, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.DefaultVsys = o.DefaultVsys
+ entry.Description = o.Description
+ entry.Devices = util.StrToEnt(o.Devices)
+ entry.Templates = util.StrToMem(o.Templates)
+ var nestedUserGroupSource *UserGroupSourceXml
+ if o.UserGroupSource != nil {
+ nestedUserGroupSource = &UserGroupSourceXml{}
+ if _, ok := o.Misc["UserGroupSource"]; ok {
+ nestedUserGroupSource.Misc = o.Misc["UserGroupSource"]
+ }
+ if o.UserGroupSource.MasterDevice != nil {
+ nestedUserGroupSource.MasterDevice = o.UserGroupSource.MasterDevice
+ }
+ }
+ entry.UserGroupSource = nestedUserGroupSource
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.DefaultVsys = o.DefaultVsys
+ entry.Description = o.Description
+ entry.Devices = util.EntToStr(o.Devices)
+ entry.Templates = util.MemToStr(o.Templates)
+ var nestedUserGroupSource *UserGroupSource
+ if o.UserGroupSource != nil {
+ nestedUserGroupSource = &UserGroupSource{}
+ if o.UserGroupSource.Misc != nil {
+ entry.Misc["UserGroupSource"] = o.UserGroupSource.Misc
+ }
+ if o.UserGroupSource.MasterDevice != nil {
+ nestedUserGroupSource.MasterDevice = o.UserGroupSource.MasterDevice
+ }
+ }
+ entry.UserGroupSource = nestedUserGroupSource
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.DefaultVsys, b.DefaultVsys) {
+ return false
+ }
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Devices, b.Devices) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Templates, b.Templates) {
+ return false
+ }
+ if !matchUserGroupSource(a.UserGroupSource, b.UserGroupSource) {
+ return false
+ }
+
+ return true
+}
+
+func matchUserGroupSource(a *UserGroupSource, b *UserGroupSource) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.MasterDevice, b.MasterDevice) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/panorama/template_stack/interfaces.go b/panorama/template_stack/interfaces.go
new file mode 100644
index 0000000..1e96891
--- /dev/null
+++ b/panorama/template_stack/interfaces.go
@@ -0,0 +1,7 @@
+package template_stack
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/panorama/template_stack/location.go b/panorama/template_stack/location.go
new file mode 100644
index 0000000..8880f36
--- /dev/null
+++ b/panorama/template_stack/location.go
@@ -0,0 +1,95 @@
+package template_stack
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Panorama *PanoramaLocation `json:"panorama,omitempty"`
+}
+
+type PanoramaLocation struct {
+ PanoramaDevice string `json:"panorama_device"`
+}
+
+func NewPanoramaLocation() *Location {
+ return &Location{Panorama: &PanoramaLocation{
+ PanoramaDevice: "localhost.localdomain",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Panorama != nil:
+ if o.Panorama.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Panorama.PanoramaDevice}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/panorama/template_stack/service.go b/panorama/template_stack/service.go
new file mode 100644
index 0000000..0d72d66
--- /dev/null
+++ b/panorama/template_stack/service.go
@@ -0,0 +1,281 @@
+package template_stack
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/panorama/template_variable/entry.go b/panorama/template_variable/entry.go
new file mode 100644
index 0000000..e1f73c5
--- /dev/null
+++ b/panorama/template_variable/entry.go
@@ -0,0 +1,265 @@
+package template_variable
+
+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{"variable"}
+)
+
+type Entry struct {
+ Name string
+ Description *string
+ Type *Type
+
+ Misc map[string][]generic.Xml
+}
+
+type Type struct {
+ AsNumber *string
+ DeviceId *string
+ DevicePriority *string
+ EgressMax *string
+ Fqdn *string
+ GroupId *string
+ Interface *string
+ IpNetmask *string
+ IpRange *string
+ LinkTag *string
+ QosProfile *string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Description *string `xml:"description,omitempty"`
+ Type *TypeXml `xml:"type,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type TypeXml struct {
+ AsNumber *string `xml:"as-number,omitempty"`
+ DeviceId *string `xml:"device-id,omitempty"`
+ DevicePriority *string `xml:"device-priority,omitempty"`
+ EgressMax *string `xml:"egress-max,omitempty"`
+ Fqdn *string `xml:"fqdn,omitempty"`
+ GroupId *string `xml:"group-id,omitempty"`
+ Interface *string `xml:"interface,omitempty"`
+ IpNetmask *string `xml:"ip-netmask,omitempty"`
+ IpRange *string `xml:"ip-range,omitempty"`
+ LinkTag *string `xml:"link-tag,omitempty"`
+ QosProfile *string `xml:"qos-profile,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "type" || v == "Type" {
+ return e.Type, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Description = o.Description
+ var nestedType *TypeXml
+ if o.Type != nil {
+ nestedType = &TypeXml{}
+ if _, ok := o.Misc["Type"]; ok {
+ nestedType.Misc = o.Misc["Type"]
+ }
+ if o.Type.DeviceId != nil {
+ nestedType.DeviceId = o.Type.DeviceId
+ }
+ if o.Type.Interface != nil {
+ nestedType.Interface = o.Type.Interface
+ }
+ if o.Type.QosProfile != nil {
+ nestedType.QosProfile = o.Type.QosProfile
+ }
+ if o.Type.EgressMax != nil {
+ nestedType.EgressMax = o.Type.EgressMax
+ }
+ if o.Type.LinkTag != nil {
+ nestedType.LinkTag = o.Type.LinkTag
+ }
+ if o.Type.IpNetmask != nil {
+ nestedType.IpNetmask = o.Type.IpNetmask
+ }
+ if o.Type.GroupId != nil {
+ nestedType.GroupId = o.Type.GroupId
+ }
+ if o.Type.DevicePriority != nil {
+ nestedType.DevicePriority = o.Type.DevicePriority
+ }
+ if o.Type.AsNumber != nil {
+ nestedType.AsNumber = o.Type.AsNumber
+ }
+ if o.Type.IpRange != nil {
+ nestedType.IpRange = o.Type.IpRange
+ }
+ if o.Type.Fqdn != nil {
+ nestedType.Fqdn = o.Type.Fqdn
+ }
+ }
+ entry.Type = nestedType
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Description = o.Description
+ var nestedType *Type
+ if o.Type != nil {
+ nestedType = &Type{}
+ if o.Type.Misc != nil {
+ entry.Misc["Type"] = o.Type.Misc
+ }
+ if o.Type.IpRange != nil {
+ nestedType.IpRange = o.Type.IpRange
+ }
+ if o.Type.Fqdn != nil {
+ nestedType.Fqdn = o.Type.Fqdn
+ }
+ if o.Type.DevicePriority != nil {
+ nestedType.DevicePriority = o.Type.DevicePriority
+ }
+ if o.Type.AsNumber != nil {
+ nestedType.AsNumber = o.Type.AsNumber
+ }
+ if o.Type.IpNetmask != nil {
+ nestedType.IpNetmask = o.Type.IpNetmask
+ }
+ if o.Type.GroupId != nil {
+ nestedType.GroupId = o.Type.GroupId
+ }
+ if o.Type.DeviceId != nil {
+ nestedType.DeviceId = o.Type.DeviceId
+ }
+ if o.Type.Interface != nil {
+ nestedType.Interface = o.Type.Interface
+ }
+ if o.Type.QosProfile != nil {
+ nestedType.QosProfile = o.Type.QosProfile
+ }
+ if o.Type.EgressMax != nil {
+ nestedType.EgressMax = o.Type.EgressMax
+ }
+ if o.Type.LinkTag != nil {
+ nestedType.LinkTag = o.Type.LinkTag
+ }
+ }
+ entry.Type = nestedType
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !matchType(a.Type, b.Type) {
+ return false
+ }
+
+ return true
+}
+
+func matchType(a *Type, b *Type) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.AsNumber, b.AsNumber) {
+ return false
+ }
+ if !util.StringsMatch(a.IpRange, b.IpRange) {
+ return false
+ }
+ if !util.StringsMatch(a.Fqdn, b.Fqdn) {
+ return false
+ }
+ if !util.StringsMatch(a.DevicePriority, b.DevicePriority) {
+ return false
+ }
+ if !util.StringsMatch(a.Interface, b.Interface) {
+ return false
+ }
+ if !util.StringsMatch(a.QosProfile, b.QosProfile) {
+ return false
+ }
+ if !util.StringsMatch(a.EgressMax, b.EgressMax) {
+ return false
+ }
+ if !util.StringsMatch(a.LinkTag, b.LinkTag) {
+ return false
+ }
+ if !util.StringsMatch(a.IpNetmask, b.IpNetmask) {
+ return false
+ }
+ if !util.StringsMatch(a.GroupId, b.GroupId) {
+ return false
+ }
+ if !util.StringsMatch(a.DeviceId, b.DeviceId) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
diff --git a/panorama/template_variable/interfaces.go b/panorama/template_variable/interfaces.go
new file mode 100644
index 0000000..94d2433
--- /dev/null
+++ b/panorama/template_variable/interfaces.go
@@ -0,0 +1,7 @@
+package template_variable
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/panorama/template_variable/location.go b/panorama/template_variable/location.go
new file mode 100644
index 0000000..b34ec41
--- /dev/null
+++ b/panorama/template_variable/location.go
@@ -0,0 +1,105 @@
+package template_variable
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ Template *TemplateLocation `json:"template,omitempty"`
+}
+
+type TemplateLocation struct {
+ PanoramaDevice string `json:"panorama_device"`
+ Template string `json:"template"`
+}
+
+func NewTemplateLocation() *Location {
+ return &Location{Template: &TemplateLocation{
+ PanoramaDevice: "localhost.localdomain",
+ Template: "",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.Template != nil:
+ if o.Template.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return fmt.Errorf("Template is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.Template != nil:
+ if o.Template.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.Template.Template == "" {
+ return nil, fmt.Errorf("Template is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.Template.PanoramaDevice}),
+ "template",
+ util.AsEntryXpath([]string{o.Template.Template}),
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/panorama/template_variable/service.go b/panorama/template_variable/service.go
new file mode 100644
index 0000000..0d6a236
--- /dev/null
+++ b/panorama/template_variable/service.go
@@ -0,0 +1,281 @@
+package template_variable
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, usePanosConfig bool) (*Entry, error) {
+ if value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string) error {
+ for _, value := range values {
+ if value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ path, err = loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
diff --git a/policies/rules/security/const.go b/policies/rules/security/const.go
new file mode 100644
index 0000000..802e05e
--- /dev/null
+++ b/policies/rules/security/const.go
@@ -0,0 +1,3 @@
+package security
+
+const RuleType = "security"
diff --git a/policies/rules/security/entry.go b/policies/rules/security/entry.go
new file mode 100644
index 0000000..987d4d1
--- /dev/null
+++ b/policies/rules/security/entry.go
@@ -0,0 +1,525 @@
+package security
+
+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{"security", "rules"}
+)
+
+type Entry struct {
+ Name string
+ Action *string
+ Applications []string
+ Categories []string
+ Description *string
+ DestinationAddresses []string
+ DestinationHips []string
+ DestinationZones []string
+ DisableServerResponseInspection *bool
+ Disabled *bool
+ IcmpUnreachable *bool
+ LogEnd *bool
+ LogSetting *string
+ LogStart *bool
+ NegateDestination *bool
+ NegateSource *bool
+ ProfileSetting *ProfileSetting
+ RuleType *string
+ Services []string
+ SourceAddresses []string
+ SourceHips []string
+ SourceUsers []string
+ SourceZones []string
+ Tags []string
+ Uuid *string
+
+ Misc map[string][]generic.Xml
+}
+
+type ProfileSetting struct {
+ Group *string
+ Profiles *ProfileSettingProfiles
+}
+type ProfileSettingProfiles struct {
+ DataFiltering []string
+ FileBlocking []string
+ Spyware []string
+ UrlFiltering []string
+ Virus []string
+ Vulnerability []string
+ WildfireAnalysis []string
+}
+
+type entryXmlContainer struct {
+ Answer []entryXml `xml:"entry"`
+}
+
+type entryXml struct {
+ XMLName xml.Name `xml:"entry"`
+ Name string `xml:"name,attr"`
+ Action *string `xml:"action,omitempty"`
+ Applications *util.MemberType `xml:"application,omitempty"`
+ Categories *util.MemberType `xml:"category,omitempty"`
+ Description *string `xml:"description,omitempty"`
+ DestinationAddresses *util.MemberType `xml:"destination,omitempty"`
+ DestinationHips *util.MemberType `xml:"destination-hip,omitempty"`
+ DestinationZones *util.MemberType `xml:"to,omitempty"`
+ DisableServerResponseInspection *string `xml:"option>disable-server-response-inspection,omitempty"`
+ Disabled *string `xml:"disabled,omitempty"`
+ IcmpUnreachable *string `xml:"icmp-unreachable,omitempty"`
+ LogEnd *string `xml:"log-end,omitempty"`
+ LogSetting *string `xml:"log-setting,omitempty"`
+ LogStart *string `xml:"log-start,omitempty"`
+ NegateDestination *string `xml:"negate-destination,omitempty"`
+ NegateSource *string `xml:"negate-source,omitempty"`
+ ProfileSetting *ProfileSettingXml `xml:"profile-setting,omitempty"`
+ RuleType *string `xml:"rule-type,omitempty"`
+ Services *util.MemberType `xml:"service,omitempty"`
+ SourceAddresses *util.MemberType `xml:"source,omitempty"`
+ SourceHips *util.MemberType `xml:"source-hip,omitempty"`
+ SourceUsers *util.MemberType `xml:"source-user,omitempty"`
+ SourceZones *util.MemberType `xml:"from,omitempty"`
+ Tags *util.MemberType `xml:"tag,omitempty"`
+ Uuid *string `xml:"uuid,attr,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+type ProfileSettingXml struct {
+ Group *string `xml:"group,omitempty"`
+ Profiles *ProfileSettingProfilesXml `xml:"profiles,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+type ProfileSettingProfilesXml struct {
+ DataFiltering *util.MemberType `xml:"data-filtering,omitempty"`
+ FileBlocking *util.MemberType `xml:"file-blocking,omitempty"`
+ Spyware *util.MemberType `xml:"spyware,omitempty"`
+ UrlFiltering *util.MemberType `xml:"url-filtering,omitempty"`
+ Virus *util.MemberType `xml:"virus,omitempty"`
+ Vulnerability *util.MemberType `xml:"vulnerability,omitempty"`
+ WildfireAnalysis *util.MemberType `xml:"wildfire-analysis,omitempty"`
+
+ Misc []generic.Xml `xml:",any"`
+}
+
+func (e *Entry) Field(v string) (any, error) {
+ if v == "name" || v == "Name" {
+ return e.Name, nil
+ }
+ if v == "action" || v == "Action" {
+ return e.Action, nil
+ }
+ if v == "applications" || v == "Applications" {
+ return e.Applications, nil
+ }
+ if v == "applications|LENGTH" || v == "Applications|LENGTH" {
+ return int64(len(e.Applications)), nil
+ }
+ if v == "categories" || v == "Categories" {
+ return e.Categories, nil
+ }
+ if v == "categories|LENGTH" || v == "Categories|LENGTH" {
+ return int64(len(e.Categories)), nil
+ }
+ if v == "description" || v == "Description" {
+ return e.Description, nil
+ }
+ if v == "destination_addresses" || v == "DestinationAddresses" {
+ return e.DestinationAddresses, nil
+ }
+ if v == "destination_addresses|LENGTH" || v == "DestinationAddresses|LENGTH" {
+ return int64(len(e.DestinationAddresses)), nil
+ }
+ if v == "destination_hips" || v == "DestinationHips" {
+ return e.DestinationHips, nil
+ }
+ if v == "destination_hips|LENGTH" || v == "DestinationHips|LENGTH" {
+ return int64(len(e.DestinationHips)), nil
+ }
+ if v == "destination_zones" || v == "DestinationZones" {
+ return e.DestinationZones, nil
+ }
+ if v == "destination_zones|LENGTH" || v == "DestinationZones|LENGTH" {
+ return int64(len(e.DestinationZones)), nil
+ }
+ if v == "disable_server_response_inspection" || v == "DisableServerResponseInspection" {
+ return e.DisableServerResponseInspection, nil
+ }
+ if v == "disabled" || v == "Disabled" {
+ return e.Disabled, nil
+ }
+ if v == "icmp_unreachable" || v == "IcmpUnreachable" {
+ return e.IcmpUnreachable, nil
+ }
+ if v == "log_end" || v == "LogEnd" {
+ return e.LogEnd, nil
+ }
+ if v == "log_setting" || v == "LogSetting" {
+ return e.LogSetting, nil
+ }
+ if v == "log_start" || v == "LogStart" {
+ return e.LogStart, nil
+ }
+ if v == "negate_destination" || v == "NegateDestination" {
+ return e.NegateDestination, nil
+ }
+ if v == "negate_source" || v == "NegateSource" {
+ return e.NegateSource, nil
+ }
+ if v == "profile_setting" || v == "ProfileSetting" {
+ return e.ProfileSetting, nil
+ }
+ if v == "rule_type" || v == "RuleType" {
+ return e.RuleType, nil
+ }
+ if v == "services" || v == "Services" {
+ return e.Services, nil
+ }
+ if v == "services|LENGTH" || v == "Services|LENGTH" {
+ return int64(len(e.Services)), nil
+ }
+ if v == "source_addresses" || v == "SourceAddresses" {
+ return e.SourceAddresses, nil
+ }
+ if v == "source_addresses|LENGTH" || v == "SourceAddresses|LENGTH" {
+ return int64(len(e.SourceAddresses)), nil
+ }
+ if v == "source_hips" || v == "SourceHips" {
+ return e.SourceHips, nil
+ }
+ if v == "source_hips|LENGTH" || v == "SourceHips|LENGTH" {
+ return int64(len(e.SourceHips)), nil
+ }
+ if v == "source_users" || v == "SourceUsers" {
+ return e.SourceUsers, nil
+ }
+ if v == "source_users|LENGTH" || v == "SourceUsers|LENGTH" {
+ return int64(len(e.SourceUsers)), nil
+ }
+ if v == "source_zones" || v == "SourceZones" {
+ return e.SourceZones, nil
+ }
+ if v == "source_zones|LENGTH" || v == "SourceZones|LENGTH" {
+ return int64(len(e.SourceZones)), nil
+ }
+ if v == "tags" || v == "Tags" {
+ return e.Tags, nil
+ }
+ if v == "tags|LENGTH" || v == "Tags|LENGTH" {
+ return int64(len(e.Tags)), nil
+ }
+ if v == "uuid" || v == "Uuid" {
+ return e.Uuid, nil
+ }
+
+ return nil, fmt.Errorf("unknown field")
+}
+
+func Versioning(vn version.Number) (Specifier, Normalizer, error) {
+ return specifyEntry, &entryXmlContainer{}, nil
+}
+
+func specifyEntry(o *Entry) (any, error) {
+ entry := entryXml{}
+
+ entry.Name = o.Name
+ entry.Action = o.Action
+ entry.Applications = util.StrToMem(o.Applications)
+ entry.Categories = util.StrToMem(o.Categories)
+ entry.Description = o.Description
+ entry.DestinationAddresses = util.StrToMem(o.DestinationAddresses)
+ entry.DestinationHips = util.StrToMem(o.DestinationHips)
+ entry.DestinationZones = util.StrToMem(o.DestinationZones)
+ entry.DisableServerResponseInspection = util.YesNo(o.DisableServerResponseInspection, nil)
+ entry.Disabled = util.YesNo(o.Disabled, nil)
+ entry.IcmpUnreachable = util.YesNo(o.IcmpUnreachable, nil)
+ entry.LogEnd = util.YesNo(o.LogEnd, nil)
+ entry.LogSetting = o.LogSetting
+ entry.LogStart = util.YesNo(o.LogStart, nil)
+ entry.NegateDestination = util.YesNo(o.NegateDestination, nil)
+ entry.NegateSource = util.YesNo(o.NegateSource, nil)
+ var nestedProfileSetting *ProfileSettingXml
+ if o.ProfileSetting != nil {
+ nestedProfileSetting = &ProfileSettingXml{}
+ if _, ok := o.Misc["ProfileSetting"]; ok {
+ nestedProfileSetting.Misc = o.Misc["ProfileSetting"]
+ }
+ if o.ProfileSetting.Group != nil {
+ nestedProfileSetting.Group = o.ProfileSetting.Group
+ }
+ if o.ProfileSetting.Profiles != nil {
+ nestedProfileSetting.Profiles = &ProfileSettingProfilesXml{}
+ if _, ok := o.Misc["ProfileSettingProfiles"]; ok {
+ nestedProfileSetting.Profiles.Misc = o.Misc["ProfileSettingProfiles"]
+ }
+ if o.ProfileSetting.Profiles.UrlFiltering != nil {
+ nestedProfileSetting.Profiles.UrlFiltering = util.StrToMem(o.ProfileSetting.Profiles.UrlFiltering)
+ }
+ if o.ProfileSetting.Profiles.FileBlocking != nil {
+ nestedProfileSetting.Profiles.FileBlocking = util.StrToMem(o.ProfileSetting.Profiles.FileBlocking)
+ }
+ if o.ProfileSetting.Profiles.WildfireAnalysis != nil {
+ nestedProfileSetting.Profiles.WildfireAnalysis = util.StrToMem(o.ProfileSetting.Profiles.WildfireAnalysis)
+ }
+ if o.ProfileSetting.Profiles.DataFiltering != nil {
+ nestedProfileSetting.Profiles.DataFiltering = util.StrToMem(o.ProfileSetting.Profiles.DataFiltering)
+ }
+ if o.ProfileSetting.Profiles.Virus != nil {
+ nestedProfileSetting.Profiles.Virus = util.StrToMem(o.ProfileSetting.Profiles.Virus)
+ }
+ if o.ProfileSetting.Profiles.Spyware != nil {
+ nestedProfileSetting.Profiles.Spyware = util.StrToMem(o.ProfileSetting.Profiles.Spyware)
+ }
+ if o.ProfileSetting.Profiles.Vulnerability != nil {
+ nestedProfileSetting.Profiles.Vulnerability = util.StrToMem(o.ProfileSetting.Profiles.Vulnerability)
+ }
+ }
+ }
+ entry.ProfileSetting = nestedProfileSetting
+
+ entry.RuleType = o.RuleType
+ entry.Services = util.StrToMem(o.Services)
+ entry.SourceAddresses = util.StrToMem(o.SourceAddresses)
+ entry.SourceHips = util.StrToMem(o.SourceHips)
+ entry.SourceUsers = util.StrToMem(o.SourceUsers)
+ entry.SourceZones = util.StrToMem(o.SourceZones)
+ entry.Tags = util.StrToMem(o.Tags)
+ entry.Uuid = o.Uuid
+
+ entry.Misc = o.Misc["Entry"]
+
+ return entry, nil
+}
+func (c *entryXmlContainer) Normalize() ([]*Entry, error) {
+ entryList := make([]*Entry, 0, len(c.Answer))
+ for _, o := range c.Answer {
+ entry := &Entry{
+ Misc: make(map[string][]generic.Xml),
+ }
+ entry.Name = o.Name
+ entry.Action = o.Action
+ entry.Applications = util.MemToStr(o.Applications)
+ entry.Categories = util.MemToStr(o.Categories)
+ entry.Description = o.Description
+ entry.DestinationAddresses = util.MemToStr(o.DestinationAddresses)
+ entry.DestinationHips = util.MemToStr(o.DestinationHips)
+ entry.DestinationZones = util.MemToStr(o.DestinationZones)
+ entry.DisableServerResponseInspection = util.AsBool(o.DisableServerResponseInspection, nil)
+ entry.Disabled = util.AsBool(o.Disabled, nil)
+ entry.IcmpUnreachable = util.AsBool(o.IcmpUnreachable, nil)
+ entry.LogEnd = util.AsBool(o.LogEnd, nil)
+ entry.LogSetting = o.LogSetting
+ entry.LogStart = util.AsBool(o.LogStart, nil)
+ entry.NegateDestination = util.AsBool(o.NegateDestination, nil)
+ entry.NegateSource = util.AsBool(o.NegateSource, nil)
+ var nestedProfileSetting *ProfileSetting
+ if o.ProfileSetting != nil {
+ nestedProfileSetting = &ProfileSetting{}
+ if o.ProfileSetting.Misc != nil {
+ entry.Misc["ProfileSetting"] = o.ProfileSetting.Misc
+ }
+ if o.ProfileSetting.Group != nil {
+ nestedProfileSetting.Group = o.ProfileSetting.Group
+ }
+ if o.ProfileSetting.Profiles != nil {
+ nestedProfileSetting.Profiles = &ProfileSettingProfiles{}
+ if o.ProfileSetting.Profiles.Misc != nil {
+ entry.Misc["ProfileSettingProfiles"] = o.ProfileSetting.Profiles.Misc
+ }
+ if o.ProfileSetting.Profiles.DataFiltering != nil {
+ nestedProfileSetting.Profiles.DataFiltering = util.MemToStr(o.ProfileSetting.Profiles.DataFiltering)
+ }
+ if o.ProfileSetting.Profiles.Virus != nil {
+ nestedProfileSetting.Profiles.Virus = util.MemToStr(o.ProfileSetting.Profiles.Virus)
+ }
+ if o.ProfileSetting.Profiles.Spyware != nil {
+ nestedProfileSetting.Profiles.Spyware = util.MemToStr(o.ProfileSetting.Profiles.Spyware)
+ }
+ if o.ProfileSetting.Profiles.Vulnerability != nil {
+ nestedProfileSetting.Profiles.Vulnerability = util.MemToStr(o.ProfileSetting.Profiles.Vulnerability)
+ }
+ if o.ProfileSetting.Profiles.UrlFiltering != nil {
+ nestedProfileSetting.Profiles.UrlFiltering = util.MemToStr(o.ProfileSetting.Profiles.UrlFiltering)
+ }
+ if o.ProfileSetting.Profiles.FileBlocking != nil {
+ nestedProfileSetting.Profiles.FileBlocking = util.MemToStr(o.ProfileSetting.Profiles.FileBlocking)
+ }
+ if o.ProfileSetting.Profiles.WildfireAnalysis != nil {
+ nestedProfileSetting.Profiles.WildfireAnalysis = util.MemToStr(o.ProfileSetting.Profiles.WildfireAnalysis)
+ }
+ }
+ }
+ entry.ProfileSetting = nestedProfileSetting
+
+ entry.RuleType = o.RuleType
+ entry.Services = util.MemToStr(o.Services)
+ entry.SourceAddresses = util.MemToStr(o.SourceAddresses)
+ entry.SourceHips = util.MemToStr(o.SourceHips)
+ entry.SourceUsers = util.MemToStr(o.SourceUsers)
+ entry.SourceZones = util.MemToStr(o.SourceZones)
+ entry.Tags = util.MemToStr(o.Tags)
+ entry.Uuid = o.Uuid
+
+ entry.Misc["Entry"] = o.Misc
+
+ entryList = append(entryList, entry)
+ }
+
+ return entryList, nil
+}
+
+func SpecMatches(a, b *Entry) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+
+ // Don't compare Name.
+ if !util.StringsMatch(a.Action, b.Action) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Applications, b.Applications) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Categories, b.Categories) {
+ return false
+ }
+ if !util.StringsMatch(a.Description, b.Description) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.DestinationAddresses, b.DestinationAddresses) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.DestinationHips, b.DestinationHips) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.DestinationZones, b.DestinationZones) {
+ return false
+ }
+ if !util.BoolsMatch(a.DisableServerResponseInspection, b.DisableServerResponseInspection) {
+ return false
+ }
+ if !util.BoolsMatch(a.Disabled, b.Disabled) {
+ return false
+ }
+ if !util.BoolsMatch(a.IcmpUnreachable, b.IcmpUnreachable) {
+ return false
+ }
+ if !util.BoolsMatch(a.LogEnd, b.LogEnd) {
+ return false
+ }
+ if !util.StringsMatch(a.LogSetting, b.LogSetting) {
+ return false
+ }
+ if !util.BoolsMatch(a.LogStart, b.LogStart) {
+ return false
+ }
+ if !util.BoolsMatch(a.NegateDestination, b.NegateDestination) {
+ return false
+ }
+ if !util.BoolsMatch(a.NegateSource, b.NegateSource) {
+ return false
+ }
+ if !matchProfileSetting(a.ProfileSetting, b.ProfileSetting) {
+ return false
+ }
+ if !util.StringsMatch(a.RuleType, b.RuleType) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Services, b.Services) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.SourceAddresses, b.SourceAddresses) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.SourceHips, b.SourceHips) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.SourceUsers, b.SourceUsers) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.SourceZones, b.SourceZones) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Tags, b.Tags) {
+ return false
+ }
+ if !util.StringsMatch(a.Uuid, b.Uuid) {
+ return false
+ }
+
+ return true
+}
+
+func matchProfileSettingProfiles(a *ProfileSettingProfiles, b *ProfileSettingProfiles) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.OrderedListsMatch(a.FileBlocking, b.FileBlocking) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.WildfireAnalysis, b.WildfireAnalysis) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.DataFiltering, b.DataFiltering) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Virus, b.Virus) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Spyware, b.Spyware) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.Vulnerability, b.Vulnerability) {
+ return false
+ }
+ if !util.OrderedListsMatch(a.UrlFiltering, b.UrlFiltering) {
+ return false
+ }
+ return true
+}
+func matchProfileSetting(a *ProfileSetting, b *ProfileSetting) bool {
+ if a == nil && b != nil || a != nil && b == nil {
+ return false
+ } else if a == nil && b == nil {
+ return true
+ }
+ if !util.StringsMatch(a.Group, b.Group) {
+ return false
+ }
+ if !matchProfileSettingProfiles(a.Profiles, b.Profiles) {
+ return false
+ }
+ return true
+}
+
+func (o *Entry) EntryName() string {
+ return o.Name
+}
+
+func (o *Entry) SetEntryName(name string) {
+ o.Name = name
+}
+func (o *Entry) EntryUuid() *string {
+ return o.Uuid
+}
+
+func (o *Entry) SetEntryUuid(uuid *string) {
+ o.Uuid = uuid
+}
diff --git a/policies/rules/security/interfaces.go b/policies/rules/security/interfaces.go
new file mode 100644
index 0000000..d15711c
--- /dev/null
+++ b/policies/rules/security/interfaces.go
@@ -0,0 +1,7 @@
+package security
+
+type Specifier func(*Entry) (any, error)
+
+type Normalizer interface {
+ Normalize() ([]*Entry, error)
+}
diff --git a/policies/rules/security/location.go b/policies/rules/security/location.go
new file mode 100644
index 0000000..d24cdb8
--- /dev/null
+++ b/policies/rules/security/location.go
@@ -0,0 +1,210 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+)
+
+type ImportLocation interface {
+ XpathForLocation(version.Number, util.ILocation) ([]string, error)
+ MarshalPangoXML([]string) (string, error)
+ UnmarshalPangoXML([]byte) ([]string, error)
+}
+
+type Location struct {
+ DeviceGroup *DeviceGroupLocation `json:"device_group,omitempty"`
+ FromPanoramaVsys *FromPanoramaVsysLocation `json:"from_panorama_vsys,omitempty"`
+ Shared *SharedLocation `json:"shared,omitempty"`
+ Vsys *VsysLocation `json:"vsys,omitempty"`
+}
+
+type DeviceGroupLocation struct {
+ DeviceGroup string `json:"device_group"`
+ PanoramaDevice string `json:"panorama_device"`
+ Rulebase string `json:"rulebase"`
+}
+
+type FromPanoramaVsysLocation struct {
+ Vsys string `json:"vsys"`
+}
+
+type SharedLocation struct {
+ Rulebase string `json:"rulebase"`
+}
+
+type VsysLocation struct {
+ NgfwDevice string `json:"ngfw_device"`
+ Rulebase string `json:"rulebase"`
+ Vsys string `json:"vsys"`
+}
+
+func NewDeviceGroupLocation() *Location {
+ return &Location{DeviceGroup: &DeviceGroupLocation{
+ DeviceGroup: "",
+ PanoramaDevice: "localhost.localdomain",
+ Rulebase: "pre-rulebase",
+ },
+ }
+}
+func NewFromPanoramaVsysLocation() *Location {
+ return &Location{FromPanoramaVsys: &FromPanoramaVsysLocation{
+ Vsys: "vsys1",
+ },
+ }
+}
+func NewSharedLocation() *Location {
+ return &Location{Shared: &SharedLocation{
+ Rulebase: "pre-rulebase",
+ },
+ }
+}
+func NewVsysLocation() *Location {
+ return &Location{Vsys: &VsysLocation{
+ NgfwDevice: "localhost.localdomain",
+ Rulebase: "pre-rulebase",
+ Vsys: "vsys1",
+ },
+ }
+}
+
+func (o Location) IsValid() error {
+ count := 0
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.DeviceGroup.Rulebase == "" {
+ return fmt.Errorf("Rulebase is unspecified")
+ }
+ count++
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ case o.Shared != nil:
+ if o.Shared.Rulebase == "" {
+ return fmt.Errorf("Rulebase is unspecified")
+ }
+ count++
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Rulebase == "" {
+ return fmt.Errorf("Rulebase is unspecified")
+ }
+ if o.Vsys.Vsys == "" {
+ return fmt.Errorf("Vsys is unspecified")
+ }
+ count++
+ }
+
+ 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) XpathPrefix(vn version.Number) ([]string, error) {
+
+ var ans []string
+
+ switch {
+ case o.DeviceGroup != nil:
+ if o.DeviceGroup.DeviceGroup == "" {
+ return nil, fmt.Errorf("DeviceGroup is unspecified")
+ }
+ if o.DeviceGroup.PanoramaDevice == "" {
+ return nil, fmt.Errorf("PanoramaDevice is unspecified")
+ }
+ if o.DeviceGroup.Rulebase == "" {
+ return nil, fmt.Errorf("Rulebase is unspecified")
+ }
+ ans = []string{
+ "config",
+ "devices",
+ util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),
+ "device-group",
+ util.AsEntryXpath([]string{o.DeviceGroup.DeviceGroup}),
+ o.DeviceGroup.Rulebase,
+ }
+ case o.FromPanoramaVsys != nil:
+ if o.FromPanoramaVsys.Vsys == "" {
+ return nil, fmt.Errorf("Vsys is unspecified")
+ }
+ ans = []string{
+ "config",
+ "panorama",
+ "vsys",
+ util.AsEntryXpath([]string{o.FromPanoramaVsys.Vsys}),
+ "rulebase",
+ }
+ case o.Shared != nil:
+ if o.Shared.Rulebase == "" {
+ return nil, fmt.Errorf("Rulebase is unspecified")
+ }
+ ans = []string{
+ "config",
+ "shared",
+ o.Shared.Rulebase,
+ }
+ case o.Vsys != nil:
+ if o.Vsys.NgfwDevice == "" {
+ return nil, fmt.Errorf("NgfwDevice is unspecified")
+ }
+ if o.Vsys.Rulebase == "" {
+ return nil, fmt.Errorf("Rulebase 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}),
+ "rulebase",
+ }
+ default:
+ return nil, errors.NoLocationSpecifiedError
+ }
+
+ return ans, nil
+}
+func (o Location) XpathWithEntryName(vn version.Number, name string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsEntryXpath([]string{name}))
+
+ return ans, nil
+}
+func (o Location) XpathWithUuid(vn version.Number, uuid string) ([]string, error) {
+
+ ans, err := o.XpathPrefix(vn)
+ if err != nil {
+ return nil, err
+ }
+ ans = append(ans, Suffix...)
+ ans = append(ans, util.AsUuidXpath(uuid))
+
+ return ans, nil
+}
diff --git a/policies/rules/security/service.go b/policies/rules/security/service.go
new file mode 100644
index 0000000..a61fa7f
--- /dev/null
+++ b/policies/rules/security/service.go
@@ -0,0 +1,862 @@
+package security
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/PaloAltoNetworks/pango/audit"
+ "github.com/PaloAltoNetworks/pango/errors"
+ "github.com/PaloAltoNetworks/pango/filtering"
+ "github.com/PaloAltoNetworks/pango/rule"
+ "github.com/PaloAltoNetworks/pango/util"
+ "github.com/PaloAltoNetworks/pango/version"
+ "github.com/PaloAltoNetworks/pango/xmlapi"
+)
+
+type Service struct {
+ client util.PangoClient
+}
+
+func NewService(client util.PangoClient) *Service {
+ return &Service{
+ client: client,
+ }
+}
+
+// Create adds new item, then returns the result.
+func (s *Service) Create(ctx context.Context, loc Location, entry *Entry) (*Entry, error) {
+ if entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+ createSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := &xmlapi.Config{
+ Action: "set",
+ Xpath: util.AsXpath(path[:len(path)-1]),
+ Element: createSpec,
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil {
+ return nil, err
+ }
+ return s.Read(ctx, loc, entry.Name, "get")
+}
+
+// Read returns the given config object, using the specified action ("get" or "show").
+func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) {
+ return s.read(ctx, loc, name, action, true, false)
+}
+
+// ReadById returns the given config object with specified ID, using the specified action ("get" or "show").
+func (s *Service) ReadById(ctx context.Context, loc Location, uuid, action string) (*Entry, error) {
+ return s.read(ctx, loc, uuid, action, false, false)
+}
+
+// ReadFromConfig returns the given config object from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) {
+ return s.read(ctx, loc, name, "", true, true)
+}
+
+// ReadFromConfigById returns the given config object with specified ID from the loaded XML config.
+// Requires that client.LoadPanosConfig() has been invoked.
+func (s *Service) ReadFromConfigById(ctx context.Context, loc Location, uuid string) (*Entry, error) {
+ return s.read(ctx, loc, uuid, "", false, true)
+}
+
+func (s *Service) read(ctx context.Context, loc Location, value, action string, byName, usePanosConfig bool) (*Entry, error) {
+ if byName && value == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ if !byName && value == "" {
+ return nil, errors.UuidNotSpecifiedError
+ }
+ vn := s.client.Versioning()
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var path []string
+ if byName {
+ path, err = loc.XpathWithEntryName(vn, value)
+ } else {
+ path, err = loc.XpathWithUuid(vn, value)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, errors.ObjectNotFound()
+ }
+ return nil, err
+ }
+ }
+
+ list, err := normalizer.Normalize()
+ if err != nil {
+ return nil, err
+ } else if len(list) != 1 {
+ return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list))
+ }
+
+ return list[0], nil
+}
+
+// Update updates the given config object, then returns the result.
+func (s *Service) Update(ctx context.Context, loc Location, entry *Entry, name string) (*Entry, error) {
+ return s.update(ctx, loc, entry, name, true)
+}
+
+// UpdateById updates the given config object, then returns the result.
+func (s *Service) UpdateById(ctx context.Context, loc Location, entry *Entry, uuid string) (*Entry, error) {
+ return s.update(ctx, loc, entry, uuid, false)
+}
+func (s *Service) update(ctx context.Context, loc Location, entry *Entry, value string, byName bool) (*Entry, error) {
+ if byName && entry.Name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+ if !byName && value == "" {
+ return nil, errors.UuidNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(2)
+ specifier, _, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+ var old *Entry
+ if byName {
+ if value != "" && value != entry.Name {
+ path, err := loc.XpathWithEntryName(vn, value)
+ if err != nil {
+ return nil, err
+ }
+
+ old, err = s.Read(ctx, loc, value, "get")
+
+ updates.Add(&xmlapi.Config{
+ Action: "rename",
+ Xpath: util.AsXpath(path),
+ NewName: entry.Name,
+ Target: s.client.GetTarget(),
+ })
+ } else {
+ old, err = s.Read(ctx, loc, entry.Name, "get")
+ }
+ } else {
+ old, err = s.ReadById(ctx, loc, value, "get")
+ }
+ if err != nil {
+ return nil, err
+ } else if old == nil {
+ return nil, fmt.Errorf("previous object doesn't exist for update")
+ }
+ if !SpecMatches(entry, old) {
+ path, err := loc.XpathWithEntryName(vn, entry.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ updateSpec, err := specifier(entry)
+ if err != nil {
+ return nil, err
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "edit",
+ Xpath: util.AsXpath(path),
+ Element: updateSpec,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ if len(updates.Operations) != 0 {
+ if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil {
+ return nil, err
+ }
+ }
+ if byName {
+ return s.Read(ctx, loc, entry.Name, "get")
+ } else {
+ return s.ReadById(ctx, loc, value, "get")
+ }
+}
+
+// Delete deletes the given item.
+func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error {
+ return s.delete(ctx, loc, name, true)
+}
+
+// DeleteById deletes the given item with specified ID.
+func (s *Service) DeleteById(ctx context.Context, loc Location, uuid ...string) error {
+ return s.delete(ctx, loc, uuid, false)
+}
+func (s *Service) delete(ctx context.Context, loc Location, values []string, byName bool) error {
+ for _, value := range values {
+ if byName && value == "" {
+ return errors.NameNotSpecifiedError
+ }
+ if !byName && value == "" {
+ return errors.UuidNotSpecifiedError
+ }
+ }
+
+ vn := s.client.Versioning()
+ var err error
+ deletes := xmlapi.NewMultiConfig(len(values))
+ for _, value := range values {
+ var path []string
+ if byName {
+ path, err = loc.XpathWithEntryName(vn, value)
+ } else {
+ path, err = loc.XpathWithUuid(vn, value)
+ }
+ if err != nil {
+ return err
+ }
+ deletes.Add(&xmlapi.Config{
+ Action: "delete",
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil)
+
+ return err
+}
+
+// List returns a list of objects using the given action ("get" or "show").
+// Params filter and quote are for client side filtering.
+func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, action, filter, quote, false)
+}
+
+// ListFromConfig returns a list of objects at the given location.
+// Requires that client.LoadPanosConfig() has been invoked.
+// Params filter and quote are for client side filtering.
+func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]*Entry, error) {
+ return s.list(ctx, loc, "", filter, quote, true)
+}
+
+func (s *Service) list(ctx context.Context, loc Location, action, filter, quote string, usePanosConfig bool) ([]*Entry, error) {
+ var err error
+
+ var logic *filtering.Group
+ if filter != "" {
+ logic, err = filtering.Parse(filter, quote)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ vn := s.client.Versioning()
+
+ _, normalizer, err := Versioning(vn)
+ if err != nil {
+ return nil, err
+ }
+
+ path, err := loc.XpathWithEntryName(vn, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if usePanosConfig {
+ if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil {
+ return nil, err
+ }
+ } else {
+ cmd := &xmlapi.Config{
+ Action: action,
+ Xpath: util.AsXpath(path),
+ Target: s.client.GetTarget(),
+ }
+
+ if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil {
+ if err.Error() == "No such node" && action == "show" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ }
+
+ listing, err := normalizer.Normalize()
+ if err != nil || logic == nil {
+ return listing, err
+ }
+
+ filtered := make([]*Entry, 0, len(listing))
+ for _, x := range listing {
+ ok, err := logic.Matches(x)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, nil
+}
+
+// MoveGroup arranges the given rules in the order specified.
+// Any rule with a UUID specified is ignored.
+// Only the rule names are considered for the purposes of the rule placement.
+func (s *Service) MoveGroup(ctx context.Context, loc Location, position rule.Position, entries []*Entry) error {
+ if len(entries) == 0 {
+ return nil
+ }
+
+ listing, err := s.List(ctx, loc, "get", "", "")
+ if err != nil {
+ return err
+ } else if len(listing) == 0 {
+ return fmt.Errorf("no rules present")
+ }
+
+ rp := make(map[string]int)
+ for idx, live := range listing {
+ rp[live.Name] = idx
+ }
+
+ vn := s.client.Versioning()
+ updates := xmlapi.NewMultiConfig(len(entries))
+
+ var ok, topDown bool
+ var otherIndex int
+ baseIndex := -1
+ switch {
+ case position.First != nil && *position.First:
+ topDown, baseIndex, ok, err = s.moveTop(topDown, entries, baseIndex, ok, rp, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ case position.Last != nil && *position.Last:
+ baseIndex, ok, err = s.moveBottom(entries, baseIndex, ok, rp, listing, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ case position.SomewhereAfter != nil && *position.SomewhereAfter != "":
+ topDown, baseIndex, ok, otherIndex, err = s.moveSomewhereAfter(topDown, entries, baseIndex, ok, rp, otherIndex, position, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ case position.SomewhereBefore != nil && *position.SomewhereBefore != "":
+ baseIndex, ok, otherIndex, err = s.moveSomewhereBefore(entries, baseIndex, ok, rp, otherIndex, position, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ case position.DirectlyAfter != nil && *position.DirectlyAfter != "":
+ topDown, baseIndex, ok, otherIndex, err = s.moveDirectlyAfter(topDown, entries, baseIndex, ok, rp, otherIndex, position, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ case position.DirectlyBefore != nil && *position.DirectlyBefore != "":
+ baseIndex, ok, err = s.moveDirectlyBefore(entries, baseIndex, ok, rp, otherIndex, position, loc, vn, updates)
+ if err != nil {
+ return err
+ }
+ default:
+ topDown = true
+ target := entries[0]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return fmt.Errorf("could not find rule %q for first positioning", target.Name)
+ }
+ }
+
+ var prevName, where string
+ if topDown {
+ prevName = entries[0].Name
+ where = "after"
+ } else {
+ prevName = entries[len(entries)-1].Name
+ where = "before"
+ }
+
+ for i := 1; i < len(entries); i++ {
+ err := s.moveRestOfRules(topDown, entries, i, baseIndex, rp, loc, vn, updates, where, prevName)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(updates.Operations) > 0 {
+ _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil)
+ return err
+ }
+
+ return nil
+}
+
+func (s *Service) moveRestOfRules(topDown bool, entries []*Entry, i int, baseIndex int, rp map[string]int, loc Location, vn version.Number, updates *xmlapi.MultiConfig, where string, prevName string) error {
+ var target Entry
+ var desiredIndex int
+ if topDown {
+ target = *entries[i]
+ desiredIndex = baseIndex + i
+ } else {
+ target = *entries[len(entries)-1-i]
+ desiredIndex = baseIndex - i
+ }
+
+ idx, ok := rp[target.Name]
+ if !ok {
+ return fmt.Errorf("rule %q not present", target.Name)
+ }
+
+ if idx != desiredIndex {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return err
+ }
+
+ if idx < desiredIndex {
+ for name, val := range rp {
+ if val > idx && val <= desiredIndex {
+ rp[name] = val - 1
+ }
+ }
+ } else {
+ for name, val := range rp {
+ if val < idx && val >= desiredIndex {
+ rp[name] = val + 1
+ }
+ }
+ }
+ rp[target.Name] = desiredIndex
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: where,
+ Destination: prevName,
+ Target: s.client.GetTarget(),
+ })
+ }
+
+ prevName = target.Name
+ return nil
+}
+
+func (s *Service) moveDirectlyBefore(entries []*Entry, baseIndex int, ok bool, rp map[string]int, otherIndex int, position rule.Position, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (int, bool, error) {
+ target := entries[len(entries)-1]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return 0, false, fmt.Errorf("could not find rule %q for initial positioning", target.Name)
+ }
+
+ otherIndex, ok = rp[*position.DirectlyBefore]
+ if !ok {
+ return 0, false, fmt.Errorf("could not find referenced rule %q", *position.DirectlyBefore)
+ }
+
+ if baseIndex+1 != otherIndex {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return 0, false, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == target.Name:
+ rp[name] = otherIndex
+ case val < baseIndex && val >= otherIndex:
+ rp[name] = val + 1
+ }
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "before",
+ Destination: *position.DirectlyBefore,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = otherIndex
+ }
+ return baseIndex, ok, nil
+}
+
+func (s *Service) moveDirectlyAfter(topDown bool, entries []*Entry, baseIndex int, ok bool, rp map[string]int, otherIndex int, position rule.Position, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (bool, int, bool, int, error) {
+ topDown = true
+ target := entries[0]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return false, 0, false, 0, fmt.Errorf("could not find rule %q for initial positioning", target.Name)
+ }
+
+ otherIndex, ok = rp[*position.DirectlyAfter]
+ if !ok {
+ return false, 0, false, 0, fmt.Errorf("could not find referenced rule %q for initial positioning", *position.DirectlyAfter)
+ }
+
+ if baseIndex != otherIndex+1 {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return false, 0, false, 0, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == target.Name:
+ rp[name] = otherIndex
+ case val > baseIndex && val <= otherIndex:
+ rp[name] = otherIndex - 1
+ }
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "after",
+ Destination: *position.DirectlyAfter,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = otherIndex
+ }
+ return topDown, baseIndex, ok, otherIndex, nil
+}
+
+func (s *Service) moveSomewhereBefore(entries []*Entry, baseIndex int, ok bool, rp map[string]int, otherIndex int, position rule.Position, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (int, bool, int, error) {
+ target := entries[len(entries)-1]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return 0, false, 0, fmt.Errorf("could not find rule %q for initial positioning", target.Name)
+ }
+
+ otherIndex, ok = rp[*position.SomewhereBefore]
+ if !ok {
+ return 0, false, 0, fmt.Errorf("could not find referenced rule %q", *position.SomewhereBefore)
+ }
+
+ if baseIndex > otherIndex {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return 0, false, 0, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == target.Name:
+ rp[name] = otherIndex
+ case val < baseIndex && val >= otherIndex:
+ rp[name] = val + 1
+ }
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "before",
+ Destination: *position.SomewhereBefore,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = otherIndex
+ }
+ return baseIndex, ok, otherIndex, nil
+}
+
+func (s *Service) moveSomewhereAfter(topDown bool, entries []*Entry, baseIndex int, ok bool, rp map[string]int, otherIndex int, position rule.Position, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (bool, int, bool, int, error) {
+ topDown = true
+ target := entries[0]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return false, 0, false, 0, fmt.Errorf("could not find rule %q for initial positioning", target.Name)
+ }
+
+ otherIndex, ok = rp[*position.SomewhereAfter]
+ if !ok {
+ return false, 0, false, 0, fmt.Errorf("could not find referenced rule %q for initial positioning", *position.SomewhereAfter)
+ }
+
+ if baseIndex < otherIndex {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return false, 0, false, 0, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == target.Name:
+ rp[name] = otherIndex
+ case val > baseIndex && val <= otherIndex:
+ rp[name] = otherIndex - 1
+ }
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "after",
+ Destination: *position.SomewhereAfter,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = otherIndex
+ }
+ return topDown, baseIndex, ok, otherIndex, nil
+}
+
+func (s *Service) moveBottom(entries []*Entry, baseIndex int, ok bool, rp map[string]int, listing []*Entry, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (int, bool, error) {
+ target := entries[len(entries)-1]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return 0, false, fmt.Errorf("could not find rule %q for last positioning", target.Name)
+ }
+
+ if baseIndex != len(listing)-1 {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return 0, false, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == target.Name:
+ rp[name] = len(listing) - 1
+ case val > baseIndex:
+ rp[name] = val - 1
+ }
+ }
+
+ // some versions of PAN-OS require that the destination always be set
+ var dst string
+ if !vn.Gte(util.FixedPanosVersionForMultiConfigMove) {
+ dst = "bottom"
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "bottom",
+ Destination: dst,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = len(listing) - 1
+ }
+ return baseIndex, ok, nil
+}
+
+func (s *Service) moveTop(topDown bool, entries []*Entry, baseIndex int, ok bool, rp map[string]int, loc Location, vn version.Number, updates *xmlapi.MultiConfig) (bool, int, bool, error) {
+ topDown = true
+ target := entries[0]
+
+ baseIndex, ok = rp[target.Name]
+ if !ok {
+ return false, 0, false, fmt.Errorf("could not find rule %q for first positioning", target.Name)
+ }
+
+ if baseIndex != 0 {
+ path, err := loc.XpathWithEntryName(vn, target.Name)
+ if err != nil {
+ return false, 0, false, err
+ }
+
+ for name, val := range rp {
+ switch {
+ case name == entries[0].Name:
+ rp[name] = 0
+ case val < baseIndex:
+ rp[name] = val + 1
+ }
+ }
+
+ // some versions of PAN-OS require that the destination always be set
+ var dst string
+ if !vn.Gte(util.FixedPanosVersionForMultiConfigMove) {
+ dst = "top"
+ }
+
+ updates.Add(&xmlapi.Config{
+ Action: "move",
+ Xpath: util.AsXpath(path),
+ Where: "top",
+ Destination: dst,
+ Target: s.client.GetTarget(),
+ })
+
+ baseIndex = 0
+ }
+ return topDown, baseIndex, ok, nil
+}
+
+// HitCount returns the hit count for the given rule.
+func (s *Service) HitCount(ctx context.Context, loc Location, rules ...string) ([]util.HitCount, error) {
+ switch {
+ case loc.Vsys != nil:
+ cmd := &xmlapi.Op{
+ Command: util.NewHitCountRequest(RuleType, loc.Vsys.Vsys, rules),
+ Target: s.client.GetTarget(),
+ }
+ var resp util.HitCountResponse
+
+ if _, _, err := s.client.Communicate(ctx, cmd, false, &resp); err != nil {
+ return nil, err
+ }
+
+ return resp.Results, nil
+ }
+
+ return nil, fmt.Errorf("unsupported location")
+}
+
+// SetAuditComment sets the given audit comment for the given rule.
+func (s *Service) SetAuditComment(ctx context.Context, loc Location, name, comment string) error {
+ if name == "" {
+ return errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ path, err := loc.XpathWithEntryName(vn, name)
+ if err != nil {
+ return err
+ }
+
+ cmd := &xmlapi.Op{
+ Command: audit.SetComment{
+ Xpath: util.AsXpath(path),
+ Comment: comment,
+ },
+ Target: s.client.GetTarget(),
+ }
+
+ _, _, err = s.client.Communicate(ctx, cmd, false, nil)
+ return err
+}
+
+// CurrentAuditComment gets any current uncommitted audit comment for the given rule.
+func (s *Service) CurrentAuditComment(ctx context.Context, loc Location, name string) (string, error) {
+ if name == "" {
+ return "", errors.NameNotSpecifiedError
+ }
+
+ vn := s.client.Versioning()
+
+ path, err := loc.XpathWithEntryName(vn, name)
+ if err != nil {
+ return "", err
+ }
+
+ cmd := &xmlapi.Op{
+ Command: audit.GetComment{
+ Xpath: util.AsXpath(path),
+ },
+ Target: s.client.GetTarget(),
+ }
+
+ var resp audit.UncommittedComment
+ if _, _, err = s.client.Communicate(ctx, cmd, false, &resp); err != nil {
+ return "", err
+ }
+
+ return resp.Comment, nil
+}
+
+// AuditCommentHistory returns a chunk of historical audit comment logs.
+func (s *Service) AuditCommentHistory(ctx context.Context, loc Location, name, direction string, nlogs, skip int) ([]audit.Comment, error) {
+ if name == "" {
+ return nil, errors.NameNotSpecifiedError
+ }
+
+ var err error
+ var base, vsysDg string
+ switch {
+ case loc.Vsys != nil:
+ vsysDg = loc.Vsys.Vsys
+ base = "rulebase"
+ case loc.Shared != nil:
+ vsysDg = "shared"
+ base = loc.Shared.Rulebase
+ case loc.DeviceGroup != nil:
+ vsysDg = loc.DeviceGroup.DeviceGroup
+ base = loc.DeviceGroup.Rulebase
+ }
+
+ if vsysDg == "" || base == "" {
+ return nil, fmt.Errorf("unsupported location")
+ }
+
+ query := strings.Join([]string{
+ "(subtype eq audit-comment)",
+ fmt.Sprintf("(path contains '\\'%s\\'')", name),
+ fmt.Sprintf("(path contains '%s')", RuleType),
+ fmt.Sprintf("(path contains %s)", base),
+ fmt.Sprintf("(path contains '\\'%s\\'')", vsysDg),
+ }, " and ")
+ extras := url.Values{}
+ extras.Set("uniq", "yes")
+
+ cmd := &xmlapi.Log{
+ LogType: "config",
+ Query: query,
+ Direction: direction,
+ Nlogs: nlogs,
+ Skip: skip,
+ Extras: extras,
+ }
+
+ var job util.JobResponse
+ if _, _, err = s.client.Communicate(ctx, cmd, false, &job); err != nil {
+ return nil, err
+ }
+
+ var resp audit.CommentHistory
+ if _, err = s.client.WaitForLogs(ctx, job.Id, 1*time.Second, &resp); err != nil {
+ return nil, err
+ }
+
+ if len(resp.Comments) != 0 {
+ if clock, err := s.client.Clock(ctx); err == nil {
+ for i := range resp.Comments {
+ resp.Comments[i].SetTime(clock)
+ }
+ }
+ }
+
+ return resp.Comments, nil
+}
diff --git a/util/comparison.go b/util/comparison.go
index fb350d1..fd1ccd5 100644
--- a/util/comparison.go
+++ b/util/comparison.go
@@ -71,6 +71,10 @@ func StringsMatch(a, b *string) bool {
return *a == *b
}
+func StringsEqual(a, b string) bool {
+ return a == b
+}
+
func BoolsMatch(a, b *bool) bool {
if a == nil && b == nil {
return true
@@ -78,7 +82,7 @@ func BoolsMatch(a, b *bool) bool {
return false
}
- return *a == *b
+ return *a == *b
}
func FloatsMatch(a, b *float64) bool {
@@ -88,27 +92,37 @@ func FloatsMatch(a, b *float64) bool {
return false
}
- return *a == *b
+ return *a == *b
+}
+
+func IntsMatch(a, b *int) bool {
+ if a == nil && b == nil {
+ return true
+ } else if a == nil || b == nil {
+ return false
+ }
+
+ return *a == *b
}
-func IntsMatch(a, b *int64) bool {
+func Ints64Match(a, b *int64) bool {
if a == nil && b == nil {
return true
} else if a == nil || b == nil {
return false
}
- return *a == *b
+ return *a == *b
}
func AnysMatch(a, b any) bool {
- if a == nil && b == nil {
- return true
- }
+ if a == nil && b == nil {
+ return true
+ }
- if a == nil || b == nil {
- return false
- }
+ if a == nil || b == nil {
+ return false
+ }
- return true
+ return true
}
diff --git a/util/location.go b/util/location.go
new file mode 100644
index 0000000..af6278d
--- /dev/null
+++ b/util/location.go
@@ -0,0 +1,7 @@
+package util
+
+import "github.com/PaloAltoNetworks/pango/version"
+
+type ILocation interface {
+ XpathPrefix(version.Number) ([]string, error)
+}
diff --git a/util/pangoclient.go b/util/pangoclient.go
index 5934d5f..eeff7c3 100644
--- a/util/pangoclient.go
+++ b/util/pangoclient.go
@@ -19,6 +19,7 @@ type PangoClient interface {
GetTarget() string
IsPanorama() (bool, error)
IsFirewall() (bool, error)
+ Clock(context.Context) (time.Time, error)
// Local inspection mode functions.
ReadFromConfig(context.Context, []string, bool, any) ([]byte, error)
diff --git a/util/util.go b/util/util.go
index 64c8384..b7e0b73 100644
--- a/util/util.go
+++ b/util/util.go
@@ -8,8 +8,12 @@ import (
"fmt"
"regexp"
"strings"
+
+ "github.com/PaloAltoNetworks/pango/version"
)
+var FixedPanosVersionForMultiConfigMove = version.Number{99, 99, 99, ""}
+
// VsysEntryType defines an entry config node with vsys entries underneath.
type VsysEntryType struct {
Entries []VsysEntry `xml:"entry"`
@@ -59,19 +63,37 @@ func MapToVsysEnt(e map[string][]string) *VsysEntryType {
}
// YesNo returns "yes" on true, "no" on false.
-func YesNo(v bool) string {
- if v {
- return "yes"
+func YesNo(val *bool, defaultVal *bool) *string {
+ if val == nil && defaultVal == nil {
+ return nil
+ }
+
+ result := "no"
+ if val != nil {
+ if *val {
+ result = "yes"
+ }
+ } else if *defaultVal {
+ result = "yes"
}
- return "no"
+ return &result
}
// AsBool returns true on yes, else false.
-func AsBool(val string) bool {
- if val == "yes" {
- return true
+func AsBool(val *string, defaultVal *string) *bool {
+ if val == nil && defaultVal == nil {
+ return nil
}
- return false
+
+ result := false
+ if val != nil {
+ if *val == "yes" {
+ result = true
+ }
+ } else if *defaultVal == "yes" {
+ result = true
+ }
+ return &result
}
// AsXpath makes an xpath out of the given interface.
@@ -110,7 +132,7 @@ func AsEntryXpath(vals []string) string {
// AsUuidXpath returns an xpath segment as a UUID location.
func AsUuidXpath(v string) string {
- return fmt.Sprintf("entry[@uuid='%s']", v)
+ return fmt.Sprintf("entry[@uuid='%s']", v)
}
// AsMemberXpath returns the given values as a member xpath segment.
diff --git a/xmlapi/multiconfig.go b/xmlapi/multiconfig.go
index 24a4684..bac943e 100644
--- a/xmlapi/multiconfig.go
+++ b/xmlapi/multiconfig.go
@@ -4,6 +4,8 @@ import (
"encoding/xml"
"fmt"
"strings"
+
+ "github.com/PaloAltoNetworks/pango/errors"
)
// Returns a new struct for performing multi-config operations with.
@@ -72,7 +74,7 @@ func NewMultiConfigResponse(text []byte) (*MultiConfigResponse, error) {
return nil, fmt.Errorf("no data received in the multi-config response")
}
- var ans MultiConfigResponse
+ ans := MultiConfigResponse{raw: text}
err := xml.Unmarshal(text, &ans)
return &ans, err
@@ -85,6 +87,8 @@ type MultiConfigResponse struct {
Status string `xml:"status,attr"`
Code int `xml:"code,attr"`
Results []MultiConfigResponseElement `xml:"response"`
+
+ raw []byte `xml:"-"`
}
// Ok returns if there was an error or not.
@@ -95,7 +99,10 @@ func (m *MultiConfigResponse) Ok() bool {
// Error returns the error if there was one.
func (m *MultiConfigResponse) Error() string {
if len(m.Results) == 0 {
- return ""
+ if err := errors.Parse(m.raw); err != nil {
+ return err.Error()
+ }
+ return "unknown multi-config error format"
}
r := m.Results[len(m.Results)-1]