func (s *Space) Policies() (*Policies, error) {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var policies Policies
	uri := fmt.Sprintf("https://%s/api/juniper/sd/fwpolicy-management/firewall-policies", s.Host)
	resp := r.Send("get", uri, nil, nil, nil)
	if resp.Error != nil {
		return nil, resp.Error
	}
	err := xml.Unmarshal(resp.Body, &policies)
	if err != nil {
		return nil, err
	}
	return &policies, nil
}
Policies returns a list of all firewall policies managed by Junos Space.
package junos
import (
	"encoding/xml"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"github.com/scottdware/go-rested"
)
type Addresses struct {
	Addresses []Address `xml:"address"`
}
type Address struct {
	ID          int    `xml:"id"`
	Name        string `xml:"name"`
	AddressType string `xml:"address-type"`
	Description string `xml:"description"`
	IPAddress   string `xml:"ip-address"`
	Hostname    string `xml:"host-name"`
}
type GroupMembers struct {
	Members []Member `xml:"members>member"`
}
type Member struct {
	ID   int    `xml:"id"`
	Name string `xml:"name"`
}
type Services struct {
	Services []Service `xml:"service"`
}
type Service struct {
	ID          int    `xml:"id"`
	Name        string `xml:"name"`
	IsGroup     bool   `xml:"is-group"`
	Description string `xml:"description"`
}
type Policy struct {
	ID          int    `xml:"id"`
	Name        string `xml:"name"`
	Description string `xml:"description"`
}
type Policies struct {
	Policies []Policy `xml:"firewall-policy"`
}
type SecurityDevices struct {
	XMLName xml.Name         `xml:"devices"`
	Devices []SecurityDevice `xml:"device"`
}
type SecurityDevice struct {
	ID        int    `xml:"id"`
	Family    string `xml:"device-family"`
	Platform  string `xml:"platform"`
	IPAddress string `xml:"device-ip"`
	Name      string `xml:"name"`
}
type Variables struct {
	Variables []Variable `xml:"variable-definition"`
}
type Variable struct {
	ID          int    `xml:"id"`
	Name        string `xml:"name"`
	Description string `xml:"description"`
}
type VariableManagement struct {
	Devices []SecurityDevice
	Space   *Space
}
type existingVariable struct {
	XMLName            xml.Name         `xml:"variable-definition"`
	Name               string           `xml:"name"`
	Description        string           `xml:"description"`
	Type               string           `xml:"type"`
	Version            int              `xml:"edit-version"`
	DefaultName        string           `xml:"default-name"`
	DefaultValue       string           `xml:"default-value-detail>default-value"`
	VariableValuesList []variableValues `xml:"variable-values-list>variable-values"`
}
type variableValues struct {
	XMLName       xml.Name `xml:"variable-values"`
	DeviceMOID    string   `xml:"device>moid"`
	DeviceName    string   `xml:"device>name"`
	VariableValue string   `xml:"variable-value-detail>variable-value"`
	VariableName  string   `xml:"variable-value-detail>name"`
}
type existingAddress struct {
	Name        string `xml:"name"`
	EditVersion int    `xml:"edit-version"`
	Description string `xml:"description"`
}
var addressesXML = `
<address>
    <name>%s</name>
    <address-type>%s</address-type>
    <host-name/>
    <edit-version/>
    <members/>
    <address-version>IPV4</address-version>
    <definition-type>CUSTOM</definition-type>
    <ip-address>%s</ip-address>
    <description>%s</description>
</address>
`
var dnsXML = `
<address>
    <name>%s</name>
    <address-type>%s</address-type>
    <host-name>%s</host-name>
    <edit-version/>
    <members/>
    <address-version>IPV4</address-version>
    <definition-type>CUSTOM</definition-type>
    <ip-address/>
    <description>%s</description>
</address>
`
var modifyAddressXML = `
<address>
    <name>%s</name>
    <address-type>%s</address-type>
    <host-name/>
    <edit-version>%d</edit-version>
    <members/>
    <address-version>IPV4</address-version>
    <definition-type>CUSTOM</definition-type>
    <ip-address>%s</ip-address>
    <description>%s</description>
</address>
`
var modifyDNSXML = `
<address>
    <name>%s</name>
    <address-type>%s</address-type>
    <host-name>%s</host-name>
    <edit-version>%d</edit-version>
    <members/>
    <address-version>IPV4</address-version>
    <definition-type>CUSTOM</definition-type>
    <ip-address/>
    <description>%s</description>
</address>
`
var serviceXML = `
<service>
    <name>%s</name>
    <description>%s</description>
    <is-group>false</is-group>
    <protocols>
        <protocol>
            <name>%s</name>
            <dst-port>%s</dst-port>
            <sunrpc-protocol-type>%s</sunrpc-protocol-type>
            <msrpc-protocol-type>%s</msrpc-protocol-type>
            <protocol-number>%d</protocol-number>
            <protocol-type>%s</protocol-type>
            <disable-timeout>%s</disable-timeout>
            %s
        </protocol>
    </protocols>
</service>
`
var addressGroupXML = `
<address>
    <name>%s</name>
    <address-type>GROUP</address-type>
    <host-name/>
    <edit-version/>
    <address-version>IPV4</address-version>
    <definition-type>CUSTOM</definition-type>
    <description>%s</description>
</address>
`
var serviceGroupXML = `
<service>
    <name>%s</name>
    <is-group>true</is-group>
    <description>%s</description>
</service>
`
var removeXML = `
<diff>
    <remove sel="%s/members/member[name='%s']"/>
</diff>
`
var addGroupMemberXML = `
<diff>
    <add sel="%s/members">
        <member>
            <name>%s</name>
        </member>
    </add>
</diff>
`
var renameXML = `
<diff>
    <replace sel="%s/name">
        <name>%s</name>
    </replace>
</diff>
`
var updateDeviceXML = `
<update-devices>
    <sd-ids>
        <id>%d</id>
    </sd-ids>
    <service-types>
        <service-type>POLICY</service-type>
    </service-types>
    <update-options>
        <enable-policy-rematch-srx-only>false</enable-policy-rematch-srx-only>
    </update-options>
</update-devices>
`
var publishPolicyXML = `
<publish>
    <policy-ids>
        <policy-id>%d</policy-id>
    </policy-ids>
</publish>
`
var createVariableXML = `
<variable-definition>
    <name>%s</name>
    <type>%s</type>
	<description>%s</description>
    <context>DEVICE</context>
    <default-name>%s</default-name>
    <default-value-detail>
        <default-value>%d</default-value>
    </default-value-detail>
</variable-definition>
`
var modifyVariableXML = `
<variable-definition>
    <name>%s</name>
    <type>%s</type>
	<description>%s</description>
	<edit-version>%d</edit-version>
    <context>DEVICE</context>
    <default-name>%s</default-name>
    <default-value-detail>
        <default-value>%s</default-value>
    </default-value-detail>
	<variable-values-list>
	%s
	</variable-values-list>
</variable-definition>
`
func (s *Space) getSDDeviceID(device interface{}) (int, error) {
	var err error
	var deviceID int
	ipRegex := regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
	devices, err := s.SecurityDevices()
	if err != nil {
		return 0, err
	}
	switch device.(type) {
	case int:
		deviceID = device.(int)
	case string:
		if ipRegex.MatchString(device.(string)) {
			for _, d := range devices.Devices {
				if d.IPAddress == device.(string) {
					deviceID = d.ID
				}
			}
		}
		for _, d := range devices.Devices {
			if d.Name == device.(string) {
				deviceID = d.ID
			}
		}
	}
	return deviceID, nil
}
func (s *Space) getObjectID(object interface{}, otype string) (int, error) {
	var err error
	var objectID int
	var services *Services
	ipRegex := regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d+)`)
	if otype == "service" {
		services, err = s.Services(object.(string))
	}
	objects, err := s.Addresses(object.(string))
	if err != nil {
		return 0, err
	}
	switch object.(type) {
	case int:
		objectID = object.(int)
	case string:
		if otype == "service" {
			for _, o := range services.Services {
				if o.Name == object {
					objectID = o.ID
				}
			}
		}
		if ipRegex.MatchString(object.(string)) {
			for _, o := range objects.Addresses {
				if o.IPAddress == object {
					objectID = o.ID
				}
			}
		}
		for _, o := range objects.Addresses {
			if o.Name == object {
				objectID = o.ID
			}
		}
	}
	return objectID, nil
}
func (s *Space) getPolicyID(object string) (int, error) {
	var err error
	var objectID int
	objects, err := s.Policies()
	if err != nil {
		return 0, err
	}
	for _, o := range objects.Policies {
		if o.Name == object {
			objectID = o.ID
		}
	}
	return objectID, nil
}
func (s *Space) getVariableID(variable string) (int, error) {
	var err error
	var variableID int
	vars, err := s.Variables()
	if err != nil {
		return 0, err
	}
	for _, v := range vars.Variables {
		if v.Name == variable {
			variableID = v.ID
		}
	}
	return variableID, nil
}
func (s *Space) getAddrTypeIP(address string) []string {
	var addrType, ipaddr string
	r := regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)(\/\d+)?`)
	rDNS := regexp.MustCompile(`[-\w\.]*\.(com|net|org|us|gov)$`)
	match := r.FindStringSubmatch(address)
	if rDNS.MatchString(address) {
		addrType = "DNS"
		ipaddr = address
		return []string{addrType, ipaddr}
	}
	switch match[2] {
	case "", "/32":
		addrType = "IPADDRESS"
		ipaddr = match[1]
	default:
		addrType = "NETWORK"
		ipaddr = address
	}
	return []string{addrType, ipaddr}
}
func (s *Space) modifyVariableContent(data *existingVariable, moid, firewall, address string, vid int) string {
	var varValuesList string
	for _, d := range data.VariableValuesList {
		varValuesList += fmt.Sprintf("<variable-values><device><moid>%s</moid><name>%s</name></device>", d.DeviceMOID, d.DeviceName)
		varValuesList += fmt.Sprintf("<variable-value-detail><variable-value>%s</variable-value><name>%s</name></variable-value-detail></variable-values>", d.VariableValue, d.VariableName)
	}
	varValuesList += fmt.Sprintf("<variable-values><device><moid>%s</moid><name>%s</name></device>", moid, firewall)
	varValuesList += fmt.Sprintf("<variable-value-detail><variable-value>net.juniper.jnap.sm.om.jpa.AddressEntity:%d</variable-value><name>%s</name></variable-value-detail></variable-values>", vid, address)
	return varValuesList
}
func (s *Space) Addresses(filter ...string) (*Addresses, error) {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var addresses Addresses
	query := map[string]string{
		"filter": "(global eq '')",
	}
	if len(filter) > 0 {
		query["filter"] = fmt.Sprintf("(global eq '%s')", filter[0])
	}
	uri := fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses", s.Host)
	resp := r.Send("get", uri, nil, nil, query)
	if resp.Error != nil {
		return nil, resp.Error
	}
	err := xml.Unmarshal(resp.Body, &addresses)
	if err != nil {
		return nil, err
	}
	return &addresses, nil
}
func (s *Space) AddAddress(name, ip string, description ...string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	desc := ""
	re := regexp.MustCompile(`[-\w\.]*\.(com|net|org|us|gov)$`)
	addrInfo := s.getAddrTypeIP(ip)
	if len(description) > 0 {
		desc = description[0]
	}
	address := fmt.Sprintf(addressesXML, name, addrInfo[0], addrInfo[1], desc)
	if re.MatchString(ip) {
		address = fmt.Sprintf(dnsXML, name, addrInfo[0], addrInfo[1], desc)
	}
	uri := fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses", s.Host)
	headers["Content-Type"] = contentAddress
	resp := r.Send("post", uri, []byte(address), headers, nil)
	if resp.Error != nil {
		return resp.Error
	}
	return nil
}
func (s *Space) EditAddress(name, newip string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	var existing existingAddress
	addrInfo := s.getAddrTypeIP(newip)
	re := regexp.MustCompile(`[-\w\.]*\.(com|net|org|us|gov)$`)
	objectID, err := s.getObjectID(name, "address")
	if err != nil {
		return err
	}
	uri := fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses/%d", s.Host, objectID)
	headers["Content-Type"] = contentAddress
	exResp := r.Send("get", uri, nil, headers, nil)
	if exResp.Error != nil {
		return exResp.Error
	}
	err = xml.Unmarshal(exResp.Body, &existing)
	if err != nil {
		return err
	}
	updateContent := fmt.Sprintf(modifyAddressXML, existing.Name, addrInfo[0], existing.EditVersion, addrInfo[1], existing.Description)
	if re.MatchString(name) {
		updateContent = fmt.Sprintf(modifyDNSXML, existing.Name, addrInfo[0], existing.EditVersion, addrInfo[1], existing.Description)
	}
	resp := r.Send("put", uri, []byte(updateContent), headers, nil)
	if resp.Error != nil {
		return resp.Error
	}
	return nil
}
func (s *Space) AddService(protocol, name string, ports interface{}, description string, timeout int) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	var protoNumber int
	var port, inactivity, secs string
	ptype := fmt.Sprintf("PROTOCOL_%s", strings.ToUpper(protocol))
	protocol = strings.ToUpper(protocol)
	protoNumber = 6
	if protocol == "UDP" {
		protoNumber = 17
	}
	switch ports.(type) {
	case int:
		port = strconv.Itoa(ports.(int))
	case string:
		p := strings.Split(ports.(string), "-")
		port = fmt.Sprintf("%s-%s", p[0], p[1])
	}
	inactivity = "false"
	secs = fmt.Sprintf("<inactivity-timeout>%d</inactivity-timeout>", timeout)
	if timeout == 0 {
		inactivity = "true"
		secs = "<inactivity-timeout/>"
	}
	service := fmt.Sprintf(serviceXML, name, description, name, port, protocol, protocol, protoNumber, ptype, inactivity, secs)
	headers["Content-Type"] = contentService
	uri := fmt.Sprintf("https://%s/api/juniper/sd/service-management/services", s.Host)
	resp := r.Send("post", uri, []byte(service), headers, nil)
	if resp.Error != nil {
		return resp.Error
	}
	return nil
}
func (s *Space) AddGroup(grouptype, name string, description ...string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	desc := ""
	uri := fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses", s.Host)
	addGroupXML := addressGroupXML
	content := contentAddress
	if len(description) > 0 {
		desc = description[0]
	}
	if grouptype == "service" {
		uri = fmt.Sprintf("https://%s/api/juniper/sd/service-management/services", s.Host)
		addGroupXML = serviceGroupXML
		content = contentService
	}
	groupXML := fmt.Sprintf(addGroupXML, name, desc)
	headers["Content-Type"] = content
	resp := r.Send("post", uri, []byte(groupXML), headers, nil)
	if resp.Error != nil {
		return resp.Error
	}
	return nil
}
func (s *Space) EditGroup(grouptype, action, object, name string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	var err error
	var uri, content, rel, xmlBody string
	objectID, err := s.getObjectID(name, grouptype)
	if err != nil {
		return err
	}
	if objectID != 0 {
		uri = fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses/%d", s.Host, objectID)
		content = contentAddressPatch
		rel = "address"
		if grouptype == "service" {
			uri = fmt.Sprintf("https://%s/api/juniper/sd/service-management/services/%d", s.Host, objectID)
			content = contentServicePatch
			rel = "service"
		}
		switch action {
		case "add":
			xmlBody = fmt.Sprintf(addGroupMemberXML, rel, object)
			headers["Content-Type"] = content
		case "remove":
			xmlBody = fmt.Sprintf(removeXML, rel, object)
			headers["Content-Type"] = content
		}
		resp := r.Send("patch", uri, []byte(xmlBody), headers, nil)
		if resp.Error != nil {
			return resp.Error
		}
	}
	return nil
}
func (s *Space) RenameObject(grouptype, name, newname string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	headers := map[string]string{}
	var err error
	var uri, content, rel, xmlBody string
	objectID, err := s.getObjectID(name, grouptype)
	if err != nil {
		return err
	}
	if objectID != 0 {
		uri = fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses/%d", s.Host, objectID)
		content = contentAddressPatch
		rel = "address"
		if grouptype == "service" {
			uri = fmt.Sprintf("https://%s/api/juniper/sd/service-management/services/%d", s.Host, objectID)
			content = contentServicePatch
			rel = "service"
		}
		xmlBody = fmt.Sprintf(renameXML, rel, newname)
		headers["Content-Type"] = content
		resp := r.Send("patch", uri, []byte(xmlBody), headers, nil)
		if resp.Error != nil {
			return resp.Error
		}
	}
	return nil
}
func (s *Space) DeleteObject(grouptype, name string) error {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var err error
	var uri string
	objectID, err := s.getObjectID(name, grouptype)
	if err != nil {
		return err
	}
	if objectID != 0 {
		uri = fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses/%d", s.Host, objectID)
		if grouptype == "service" {
			uri = fmt.Sprintf("https://%s/api/juniper/sd/service-management/services/%d", s.Host, objectID)
		}
		resp := r.Send("delete", uri, nil, nil, nil)
		if resp.Error != nil {
			return resp.Error
		}
	}
	return nil
}
func (s *Space) Services(filter ...string) (*Services, error) {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var services Services
	query := map[string]string{
		"filter": "(global eq '')",
	}
	if len(filter) > 0 {
		query["filter"] = fmt.Sprintf("(global eq '%s')", filter[0])
	}
	uri := fmt.Sprintf("https://%s/api/juniper/sd/service-management/services", s.Host)
	resp := r.Send("get", uri, nil, nil, query)
	if resp.Error != nil {
		return nil, resp.Error
	}
	err := xml.Unmarshal(resp.Body, &services)
	if err != nil {
		return nil, err
	}
	return &services, nil
}
func (s *Space) GroupMembers(grouptype, name string) (*GroupMembers, error) {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var members GroupMembers
	objectID, err := s.getObjectID(name, grouptype)
	uri := fmt.Sprintf("https://%s/api/juniper/sd/address-management/addresses/%d", s.Host, objectID)
	if grouptype == "service" {
		uri = fmt.Sprintf("https://%s/api/juniper/sd/service-management/services/%d", s.Host, objectID)
	}
	resp := r.Send("get", uri, nil, nil, nil)
	if resp.Error != nil {
		return nil, resp.Error
	}
	err = xml.Unmarshal(resp.Body, &members)
	if err != nil {
		return nil, err
	}
	return &members, nil
}
func (s *Space) SecurityDevices() (*SecurityDevices, error) {
	r := rested.NewRequest()
	r.BasicAuth(s.User, s.Password)
	var devices SecurityDevices
	uri := fmt.Sprintf("https://%s/api/juniper/sd/device-management/devices", s.Host)
	resp := r.Send("get", uri, nil, nil, nil)
	if resp.Error != nil {
		return nil, resp.Error
	}
	err := xml.Unmarshal(resp.Body, &devices)
	if err != nil {
		return nil, err
	}
	return &devices, nil
}