From efc4ce29c1b98e56ddf042d4f1382b87e2f800f3 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 27 May 2023 09:38:08 +0300 Subject: [PATCH 1/7] Zones API is supported. Start of the DNS API. --- main.go | 84 +++++++++++++++++++ proxmox/client.go | 90 ++++++++++++++++++++ proxmox/config_sdn.go | 185 ++++++++++++++++++++++++++++++++++++++++++ proxmox/util.go | 9 ++ 4 files changed, 368 insertions(+) create mode 100644 proxmox/config_sdn.go diff --git a/main.go b/main.go index b790e855..d572b1d0 100644 --- a/main.go +++ b/main.go @@ -785,6 +785,90 @@ func main() { } log.Printf("Network configuration on node %s has been reverted\n", node) + //SDN + case "applySDN": + exitStatus, err := c.ApplySDN() + if err != nil { + failError(fmt.Errorf("error: %+v\n api error: %s", err, exitStatus)) + } + log.Printf("SDN configuration has been applied\n") + + case "getZonesList": + zones, err := c.GetSDNZones(true, "") + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + zonesList, err := json.Marshal(zones) + failError(err) + fmt.Println(string(zonesList)) + + case "getZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: Zone name is needed")) + } + zoneName := flag.Args()[1] + zone, err := c.GetSDNZone(zoneName) + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + zoneList, err := json.Marshal(zone) + failError(err) + fmt.Println(string(zoneList)) + + case "createZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: Zone name is needed")) + } + zoneName := flag.Args()[1] + config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.CreateWithValidate(zoneName, c)) + log.Printf("Zone %s has been created\n", zoneName) + + case "deleteZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + zoneName := flag.Args()[1] + err := c.DeleteSDNZone(zoneName) + failError(err) + + case "updateZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + zoneName := flag.Args()[1] + config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.UpdateWithValidate(zoneName, c)) + log.Printf("Zone %s has been updated\n", zoneName) + + case "getDNSList": + dns, err := c.GetSDNDNSs("") + if err != nil { + log.Printf("Error listing SDN DNS entries %+v\n", err) + os.Exit(1) + } + dnsList, err := json.Marshal(dns) + failError(err) + fmt.Println(string(dnsList)) + + case "getDNS": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: DNS name is needed")) + } + name := flag.Args()[1] + dns, err := c.GetSDNDNS(name) + if err != nil { + log.Printf("Error listing SDN DNS %+v\n", err) + os.Exit(1) + } + dnsList, err := json.Marshal(dns) + failError(err) + fmt.Println(string(dnsList)) + default: fmt.Printf("unknown action, try start|stop vmid\n") } diff --git a/proxmox/client.go b/proxmox/client.go index d71dc144..48004e1e 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -30,6 +30,7 @@ type Client struct { ApiUrl string Username string Password string + ApiToken string Otp string TaskTimeout int } @@ -1837,6 +1838,95 @@ func (c *Client) RevertNetwork(node string) (exitStatus string, err error) { return c.DeleteWithTask(url) } +// SDN + +func (c *Client) ApplySDN() (string, error) { + return c.PutWithTask(nil, "/cluster/sdn") +} + +// GetSDNDNSs returns a list of all DNS definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNDNSs(typeFilter string) (list map[string]interface{}, err error) { + url := "/cluster/sdn/dns" + if typeFilter != "" { + url += fmt.Sprintf("&type=%s", typeFilter) + } + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNDNSExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNDNSExistance(id string) (existance bool, err error) { + list, err := c.GetSDNDNSs("") + existance = ItemInKeyOfArray(list["data"].([]interface{}), "dns", id) + return +} + +// GetSDNDNS returns details about the DNS entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNDNS struct. +func (c *Client) GetSDNDNS(name string) (dns map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/dns/%s", name) + err = c.GetJsonRetryable(url, &dns, 3) + return +} + +// CreateSDNDNS creates a new SDN DNS in the cluster +func (c *Client) CreateSDNDNS(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/dns") +} + +// DeleteSDNDNS deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNDNS(name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/dns/%s", name)) +} + +// UpdateSDNDNS updates the given DNS with the provided parameters +func (c *Client) UpdateSDNDNS(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/dns/"+id) +} + +// GetSDNZones returns a list of all the SDN zones defined in the cluster. +func (c *Client) GetSDNZones(pending bool, typeFilter string) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/zones?pending=%d", Btoi(pending)) + if typeFilter != "" { + url += fmt.Sprintf("&type=%s", typeFilter) + } + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNZoneExistance returns true if a zone with the provided ID exists, false otherwise. +func (c *Client) CheckSDNZoneExistance(id string) (existance bool, err error) { + list, err := c.GetSDNZones(true, "") + existance = ItemInKeyOfArray(list["data"].([]interface{}), "zone", id) + return +} + +// GetSDNZone returns details about the zone whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNZone struct. +func (c *Client) GetSDNZone(zoneName string) (zone map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/zones/%s", zoneName) + err = c.GetJsonRetryable(url, &zone, 3) + return +} + +// CreateSDNZone creates a new SDN zone in the cluster +func (c *Client) CreateSDNZone(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/zones") +} + +// DeleteSDNZone deletes an existing SDN zone in the cluster +func (c *Client) DeleteSDNZone(zoneName string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/zones/%s", zoneName)) +} + +// UpdateSDNZone updates the given zone with the provided parameters +func (c *Client) UpdateSDNZone(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/zones/"+id) +} + // Shared func (c *Client) GetItemConfigMapStringInterface(url, text, message string) (map[string]interface{}, error) { data, err := c.GetItemConfig(url, text, message) diff --git a/proxmox/config_sdn.go b/proxmox/config_sdn.go new file mode 100644 index 00000000..ea0a4568 --- /dev/null +++ b/proxmox/config_sdn.go @@ -0,0 +1,185 @@ +package proxmox + +import ( + "encoding/json" + "fmt" +) + +// ConfigSDNDNS describes the SDN DNS configurable element +type ConfigSDNDNS struct { + DNS string `json:"dns"` + Key string `json:"key"` + Type string `json:"type"` + URL string `json:"url"` + TTL int `json:"ttl,omitempty"` + // The SDN Plugin schema contains ReverseV6Mask attribute while the + // PowerDNS plugin schema contains the ReverseMaskV6 attribute + // This is probably a bug that crept into the Proxmox implementation.a + // Checked in libpve-network-perl=0.7.3 + ReverseMaskV6 int `json:"reversemaskv6,omitempty"` + ReverseV6Mask int `json:"reversev6mask,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +func NewConfigSDNDNSFromJson(input []byte) (config *ConfigSDNDNS, err error) { + config = &ConfigSDNDNS{} + err = json.Unmarshal([]byte(input), config) + return +} + +// ConfigSDNZone describes the Zone configurable element +type ConfigSDNZone struct { + Type string `json:"type"` + Zone string `json:"zone"` + AdvertiseSubnets bool `json:"advertise-subnets,omitempty"` + Bridge string `json:"bridge,omitempty"` + BridgeDisableMacLearning bool `json:"bridge-disable-mac-learning,omitempty"` + Controller string `json:"controller,omitempty"` + Delete string `json:"delete,omitempty"` + DisableARPNDSuppression bool `json:"disable-arp-nd-suppression,omitempty"` + DNS string `json:"dns,omitempty"` + DNSZone string `json:"dnszone,omitempty"` + DPID int `json:"dp-id,omitempty"` + ExitNodes string `json:"exitnodes,omitempty"` + ExitNodesLocalRouting bool `json:"exitnodes-local-routing,omitempty"` + ExitNodesPrimary string `json:"exitnodes-primary,omitempty"` + IPAM string `json:"ipam,omitempty"` + MAC string `json:"mac,omitempty"` + MTU int `json:"mtu,omitempty"` + Nodes string `json:"nodes,omitempty"` + Peers string `json:"peers,omitempty"` + ReverseDNS string `json:"reversedns,omitempty"` + RTImport string `json:"rt-import,omitempty"` + Tag int `json:"tag,omitempty"` + VlanProtocol string `json:"vlan-protocol,omitempty"` + VrfVxlan int `json:"vrf-vxlan,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +// NewConfigNetworkFromJSON takes in a byte array from a json encoded SDN Zone +// configuration and stores it in config. +// It returns the newly created config with the passed in configuration stored +// and an error if one occurs unmarshalling the input data. +func NewConfigSDNZoneFromJson(input []byte) (config *ConfigSDNZone, err error) { + config = &ConfigSDNZone{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNZone) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNZone) Create(id string, client *Client) (err error) { + config.Zone = id + params := config.mapToApiValues() + return client.CreateSDNZone(params) +} + +func (config *ConfigSDNZone) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNZone) Update(id string, client *Client) (err error) { + config.Zone = id + params := config.mapToApiValues() + err = client.UpdateSDNZone(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN Zone: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNZone) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNZoneExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "zone") + } + if !exists && !create { + return ErrorItemNotExists(id, "zone") + } + + err = ValidateStringInArray([]string{"evpn", "qinq", "simple", "vlan", "vxlan"}, c.Type, "type") + if err != nil { + return + } + switch c.Type { + case "simple": + case "vlan": + if create { + if c.Bridge == "" { + return ErrorKeyEmpty("bridge") + } + } + case "qinq": + if create { + if c.Bridge == "" { + return ErrorKeyEmpty("bridge") + } + if c.Tag <= 0 { + return ErrorKeyEmpty("tag") + } + if c.VlanProtocol == "" { + return ErrorKeyEmpty("vlan-protocol") + } + } + case "vxlan": + if create { + if c.Peers == "" { + return ErrorKeyEmpty("peers") + } + } + case "evpn": + if create { + if c.VrfVxlan < 0 { + return ErrorKeyEmpty("vrf-vxlan") + } + if c.Controller == "" { + return ErrorKeyEmpty("controller") + } + } + } + if c.VlanProtocol != "" { + err = ValidateStringInArray([]string{"802.1q", "802.1ad"}, c.VlanProtocol, "vlan-protocol") + if err != nil { + return + } + } + return +} + +func (config *ConfigSDNZone) mapToApiValues() (params map[string]interface{}) { + + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + boolsToFix := []string{ + "advertise-subnets", + "bridge-disable-mac-learning", + "disable-arp-nd-suppression", + "exitnodes-local-routing", + } + for _, key := range boolsToFix { + if v, has := params[key]; has { + params[key] = Btoi(v.(bool)) + } + } + // Remove the zone and type (path parameters) from the map + delete(params, "zone") + delete(params, "type") + return +} diff --git a/proxmox/util.go b/proxmox/util.go index 400f91e6..34eec5fe 100644 --- a/proxmox/util.go +++ b/proxmox/util.go @@ -19,6 +19,15 @@ func inArray(arr []string, str string) bool { return false } +func Btoi(b bool) int { + switch b { + case true: + return 1 + default: + return 0 + } +} + func Itob(i int) bool { return i == 1 } From 10081777d6cc5d5142d3bdd7765990da327235ce Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 27 May 2023 17:41:00 +0300 Subject: [PATCH 2/7] Added support for vnets and subnets --- main.go | 52 +++++++++++++ proxmox/client.go | 78 ++++++++++++++++++++ proxmox/config_sdn.go | 23 ------ proxmox/config_sdn_dns.go | 91 +++++++++++++++++++++++ proxmox/config_sdn_subnet.go | 139 +++++++++++++++++++++++++++++++++++ proxmox/config_sdn_vnet.go | 100 +++++++++++++++++++++++++ 6 files changed, 460 insertions(+), 23 deletions(-) create mode 100644 proxmox/config_sdn_dns.go create mode 100644 proxmox/config_sdn_subnet.go create mode 100644 proxmox/config_sdn_vnet.go diff --git a/main.go b/main.go index d572b1d0..1c0dd2a4 100644 --- a/main.go +++ b/main.go @@ -845,6 +845,58 @@ func main() { failError(config.UpdateWithValidate(zoneName, c)) log.Printf("Zone %s has been updated\n", zoneName) + case "getVNetsList": + zones, err := c.GetSDNVNets(true) + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + vnetsList, err := json.Marshal(zones) + failError(err) + fmt.Println(string(vnetsList)) + + case "getVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name is needed")) + } + vnetName := flag.Args()[1] + vnet, err := c.GetSDNVNet(vnetName) + if err != nil { + log.Printf("Error listing SDN VNets %+v\n", err) + os.Exit(1) + } + vnetsList, err := json.Marshal(vnet) + failError(err) + fmt.Println(string(vnetsList)) + + case "createVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name is needed")) + } + vnetName := flag.Args()[1] + config, err := proxmox.NewConfigSDNVNetFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.CreateWithValidate(vnetName, c)) + log.Printf("VNet %s has been created\n", vnetName) + + case "deleteVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name required")) + } + vnetName := flag.Args()[1] + err := c.DeleteSDNVNet(vnetName) + failError(err) + + case "updateVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + vnetName := flag.Args()[1] + config, err := proxmox.NewConfigSDNVNetFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.UpdateWithValidate(vnetName, c)) + log.Printf("VNet %s has been updated\n", vnetName) + case "getDNSList": dns, err := c.GetSDNDNSs("") if err != nil { diff --git a/proxmox/client.go b/proxmox/client.go index 48004e1e..3be0efd9 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -1844,6 +1844,84 @@ func (c *Client) ApplySDN() (string, error) { return c.PutWithTask(nil, "/cluster/sdn") } +// GetSDNVNets returns a list of all VNet definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNVNets(pending bool) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets?pending=%d", Btoi(pending)) + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNVNetExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNVNetExistance(id string) (existance bool, err error) { + list, err := c.GetSDNVNets(true) + existance = ItemInKeyOfArray(list["data"].([]interface{}), "vnet", id) + return +} + +// GetSDNVNet returns details about the DNS entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNVNet struct. +func (c *Client) GetSDNVNet(name string) (dns map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s", name) + err = c.GetJsonRetryable(url, &dns, 3) + return +} + +// CreateSDNVNet creates a new SDN DNS in the cluster +func (c *Client) CreateSDNVNet(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/vnets") +} + +// DeleteSDNVNet deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNVNet(name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/vnets/%s", name)) +} + +// UpdateSDNVNet updates the given DNS with the provided parameters +func (c *Client) UpdateSDNVNet(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/vnets/"+id) +} + +// GetSDNSubnets returns a list of all Subnet definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNSubnets(vnet string) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s/subnets", vnet) + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNSubnetExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNSubnetExistance(vnet, id string) (existance bool, err error) { + list, err := c.GetSDNSubnets(vnet) + existance = ItemInKeyOfArray(list["data"].([]interface{}), "subnet", id) + return +} + +// GetSDNSubnet returns details about the Subnet entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned map["data"] section can be unmarshalled into a ConfigSDNSubnet struct. +func (c *Client) GetSDNSubnet(vnet, name string) (subnet map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, name) + err = c.GetJsonRetryable(url, &subnet, 3) + return +} + +// CreateSDNSubnet creates a new SDN DNS in the cluster +func (c *Client) CreateSDNSubnet(vnet string, params map[string]interface{}) error { + return c.Post(params, fmt.Sprintf("/cluster/sdn/vnets/%s/subnets", vnet)) +} + +// DeleteSDNSubnet deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNSubnet(vnet, name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, name)) +} + +// UpdateSDNSubnet updates the given DNS with the provided parameters +func (c *Client) UpdateSDNSubnet(vnet, id string, params map[string]interface{}) error { + return c.Put(params, fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, id)) +} + // GetSDNDNSs returns a list of all DNS definitions in the "data" element of the returned // map. func (c *Client) GetSDNDNSs(typeFilter string) (list map[string]interface{}, err error) { diff --git a/proxmox/config_sdn.go b/proxmox/config_sdn.go index ea0a4568..a20933e3 100644 --- a/proxmox/config_sdn.go +++ b/proxmox/config_sdn.go @@ -5,29 +5,6 @@ import ( "fmt" ) -// ConfigSDNDNS describes the SDN DNS configurable element -type ConfigSDNDNS struct { - DNS string `json:"dns"` - Key string `json:"key"` - Type string `json:"type"` - URL string `json:"url"` - TTL int `json:"ttl,omitempty"` - // The SDN Plugin schema contains ReverseV6Mask attribute while the - // PowerDNS plugin schema contains the ReverseMaskV6 attribute - // This is probably a bug that crept into the Proxmox implementation.a - // Checked in libpve-network-perl=0.7.3 - ReverseMaskV6 int `json:"reversemaskv6,omitempty"` - ReverseV6Mask int `json:"reversev6mask,omitempty"` - // Digest allows for a form of optimistic locking - Digest string `json:"digest,omitempty"` -} - -func NewConfigSDNDNSFromJson(input []byte) (config *ConfigSDNDNS, err error) { - config = &ConfigSDNDNS{} - err = json.Unmarshal([]byte(input), config) - return -} - // ConfigSDNZone describes the Zone configurable element type ConfigSDNZone struct { Type string `json:"type"` diff --git a/proxmox/config_sdn_dns.go b/proxmox/config_sdn_dns.go new file mode 100644 index 00000000..de97d188 --- /dev/null +++ b/proxmox/config_sdn_dns.go @@ -0,0 +1,91 @@ +package proxmox + +import ( + "encoding/json" + "fmt" +) + +// ConfigSDNDNS describes the SDN DNS configurable element +type ConfigSDNDNS struct { + DNS string `json:"dns"` + Key string `json:"key"` + Type string `json:"type"` + URL string `json:"url"` + TTL int `json:"ttl,omitempty"` + // The SDN Plugin schema contains ReverseV6Mask attribute while the + // PowerDNS plugin schema contains the ReverseMaskV6 attribute + // This is probably a bug that crept into the Proxmox implementation.a + // Checked in libpve-network-perl=0.7.3 + ReverseMaskV6 int `json:"reversemaskv6,omitempty"` + ReverseV6Mask int `json:"reversev6mask,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +func NewConfigSDNDNSFromJson(input []byte) (config *ConfigSDNDNS, err error) { + config = &ConfigSDNDNS{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNDNS) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNDNS) Create(id string, client *Client) (err error) { + config.DNS = id + params := config.mapToApiValues() + return client.CreateSDNDNS(params) +} + +func (config *ConfigSDNDNS) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNDNS) Update(id string, client *Client) (err error) { + config.DNS = id + params := config.mapToApiValues() + err = client.UpdateSDNDNS(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN DNS: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNDNS) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNDNSExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "dns") + } + if !exists && !create { + return ErrorItemNotExists(id, "dns") + } + + err = ValidateStringInArray([]string{"powerdns"}, c.Type, "type") + if err != nil { + return + } + err = ValidateIntGreater(0, c.TTL, "ttl") + if err != nil { + return + } + return +} + +func (config *ConfigSDNDNS) mapToApiValues() (params map[string]interface{}) { + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + return +} diff --git a/proxmox/config_sdn_subnet.go b/proxmox/config_sdn_subnet.go new file mode 100644 index 00000000..195a3648 --- /dev/null +++ b/proxmox/config_sdn_subnet.go @@ -0,0 +1,139 @@ +package proxmox + +import ( + "encoding/json" + "fmt" + "net" +) + +/* + { + "cidr":"192.168.11.0/24", + "zone":"testlab", + "mask":"24", + "network":"192.168.11.0", + "type":"subnet", + "pending":{"gateway":"192.168.11.2"}, + "digest":null, + "snat":1, + "state":"changed", + "subnet":"testlab-192.168.11.0-24", + "vnet":"testlab1", + "gateway":"192.168.11.1" + } +*/ +type ConfigSDNSubnet struct { + // For creation purposes - Subnet is a CIDR + // Once a subnet has been created, the Subnet is an identifier with the format + // "--" + Subnet string `json:"subnet"` + + DNSZonePrefix string `json:"dnszoneprefix,omitempty"` + Gateway string `json:"gateway,omitempty"` + SNAT bool `json:"snat,omitempty"` + + // Delete is a string of attributes to be deleted from the object + Delete string `json:"delete,omitempty"` + // Type must always hold the string "subnet" + Type string `json:"type"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +// NewConfigSDNSubnetFromJSON takes in a byte array from a json encoded SDN Subnet +// configuration and stores it in config. +// It returns the newly created config with the passed in configuration stored +// and an error if one occurs unmarshalling the input data. +func NewConfigSDNSubnetFromJson(input []byte) (config *ConfigSDNSubnet, err error) { + config = &ConfigSDNSubnet{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNSubnet) CreateWithValidate(vnet, id string, client *Client) (err error) { + err = config.Validate(vnet, id, true, client) + if err != nil { + return + } + return config.Create(vnet, id, client) +} + +func (config *ConfigSDNSubnet) Create(vnet, id string, client *Client) (err error) { + config.Subnet = id + config.Type = "subnet" + params := config.mapToApiValues() + return client.CreateSDNSubnet(vnet, params) +} + +func (config *ConfigSDNSubnet) UpdateWithValidate(vnet, id string, client *Client) (err error) { + err = config.Validate(vnet, id, false, client) + if err != nil { + return + } + return config.Update(vnet, id, client) +} + +func (config *ConfigSDNSubnet) Update(vnet, id string, client *Client) (err error) { + config.Subnet = id + config.Type = "" // For some reason, this shouldn't be sent on update. Only on create. + params := config.mapToApiValues() + err = client.UpdateSDNSubnet(vnet, id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN Subnet: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNSubnet) Validate(vnet, id string, create bool, client *Client) (err error) { + vnetExists, err := client.CheckSDNVNetExistance(vnet) + if err != nil { + return + } + if !vnetExists { + return fmt.Errorf("Subnet must be created in an existing vnet. Vnet (%s) wasn't found", vnet) + } + exists, err := client.CheckSDNSubnetExistance(vnet, id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "subnet") + } + if !exists && !create { + return ErrorItemNotExists(id, "subnet") + } + + // if this is an update, the Subnet is an identifier of the form -- + // and therefore shouldn't be validated or changed + if create { + // Make sure that the CIDR is actually a valid CIDR + _, _, err = net.ParseCIDR(c.Subnet) + if err != nil { + return + } + } + + if c.Gateway != "" { + ip := net.ParseIP(c.Gateway) + if ip == nil { + return fmt.Errorf("error gateway (%s) is not a valid IP", c.Gateway) + } + } + + return +} + +func (config *ConfigSDNSubnet) mapToApiValues() (params map[string]interface{}) { + + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + if v, has := params["snat"]; has { + params["snat"] = Btoi(v.(bool)) + } + // Remove the subnet and vnet (path parameters) from the map + delete(params, "subnet") + delete(params, "vnet") + return +} diff --git a/proxmox/config_sdn_vnet.go b/proxmox/config_sdn_vnet.go new file mode 100644 index 00000000..7c6bf72c --- /dev/null +++ b/proxmox/config_sdn_vnet.go @@ -0,0 +1,100 @@ +package proxmox + +import ( + "encoding/json" + "fmt" + "regexp" +) + +type ConfigSDNVNet struct { + VNet string `json:"vnet"` + Zone string `json:"zone"` + Alias string `json:"alias,omitempty"` + Delete string `json:"delete,omitempty"` + Tag int `json:"tag,omitempty"` + VLANAware bool `json:"vlanaware,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +func NewConfigSDNVNetFromJson(input []byte) (config *ConfigSDNVNet, err error) { + config = &ConfigSDNVNet{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNVNet) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNVNet) Create(id string, client *Client) (err error) { + config.VNet = id + params := config.mapToApiValues() + return client.CreateSDNVNet(params) +} + +func (config *ConfigSDNVNet) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNVNet) Update(id string, client *Client) (err error) { + config.VNet = id + params := config.mapToApiValues() + err = client.UpdateSDNVNet(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN VNet: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNVNet) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNVNetExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "vnet") + } + if !exists && !create { + return ErrorItemNotExists(id, "vnet") + } + zoneExists, err := client.CheckSDNZoneExistance(c.Zone) + if err != nil { + return + } + if !zoneExists { + return fmt.Errorf("VNet must be associated to an existing zone. Zone %s could not be found.", c.Zone) + } + if c.Alias != "" { + regex, _ := regexp.Compile(`^(?^i:[\(\)-_.\w\d\s]{0,256})$`) + if !regex.Match([]byte(c.Alias)) { + return fmt.Errorf(`Alias must match the validation regular expression: ^(?^i:[\(\)-_.\w\d\s]{0,256})$`) + } + } + err = ValidateIntGreater(0, c.Tag, "tag") + if err != nil { + return + } + + return +} + +func (config *ConfigSDNVNet) mapToApiValues() (params map[string]interface{}) { + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + if v, has := params["vlanaware"]; has { + params["vlanaware"] = Btoi(v.(bool)) + } + + return +} From 27b12a7c996e73e0721bbc88179383d5a4a7e543 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 27 May 2023 17:41:33 +0300 Subject: [PATCH 3/7] Renamed the zone source file for consistency --- proxmox/{config_sdn.go => config_sdn_zone.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename proxmox/{config_sdn.go => config_sdn_zone.go} (100%) diff --git a/proxmox/config_sdn.go b/proxmox/config_sdn_zone.go similarity index 100% rename from proxmox/config_sdn.go rename to proxmox/config_sdn_zone.go From 42cfca805f955de28ff4569fe0fcdbc28716dc73 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 27 May 2023 18:12:35 +0300 Subject: [PATCH 4/7] commenting the delete field --- proxmox/config_sdn_zone.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxmox/config_sdn_zone.go b/proxmox/config_sdn_zone.go index a20933e3..6dd9dc3e 100644 --- a/proxmox/config_sdn_zone.go +++ b/proxmox/config_sdn_zone.go @@ -13,7 +13,6 @@ type ConfigSDNZone struct { Bridge string `json:"bridge,omitempty"` BridgeDisableMacLearning bool `json:"bridge-disable-mac-learning,omitempty"` Controller string `json:"controller,omitempty"` - Delete string `json:"delete,omitempty"` DisableARPNDSuppression bool `json:"disable-arp-nd-suppression,omitempty"` DNS string `json:"dns,omitempty"` DNSZone string `json:"dnszone,omitempty"` @@ -31,6 +30,8 @@ type ConfigSDNZone struct { Tag int `json:"tag,omitempty"` VlanProtocol string `json:"vlan-protocol,omitempty"` VrfVxlan int `json:"vrf-vxlan,omitempty"` + // Pass a string of attributes to be deleted from the remote object + Delete string `json:"delete,omitempty"` // Digest allows for a form of optimistic locking Digest string `json:"digest,omitempty"` } From 07699263d5528feac5579e988f5c4c504e315227 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 27 May 2023 18:14:31 +0300 Subject: [PATCH 5/7] removed debug comment --- proxmox/config_sdn_subnet.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/proxmox/config_sdn_subnet.go b/proxmox/config_sdn_subnet.go index 195a3648..63bcd9bf 100644 --- a/proxmox/config_sdn_subnet.go +++ b/proxmox/config_sdn_subnet.go @@ -6,22 +6,6 @@ import ( "net" ) -/* - { - "cidr":"192.168.11.0/24", - "zone":"testlab", - "mask":"24", - "network":"192.168.11.0", - "type":"subnet", - "pending":{"gateway":"192.168.11.2"}, - "digest":null, - "snat":1, - "state":"changed", - "subnet":"testlab-192.168.11.0-24", - "vnet":"testlab1", - "gateway":"192.168.11.1" - } -*/ type ConfigSDNSubnet struct { // For creation purposes - Subnet is a CIDR // Once a subnet has been created, the Subnet is an identifier with the format From 516556234691810e37e67664dcadc6a87c336b41 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Sat, 10 Jun 2023 10:00:06 +0300 Subject: [PATCH 6/7] No need for an ApiToken field --- proxmox/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/proxmox/client.go b/proxmox/client.go index baa87d58..548c2c8f 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -30,7 +30,6 @@ type Client struct { ApiUrl string Username string Password string - ApiToken string Otp string TaskTimeout int } From 4e89538a6b826b820679c80a6828488eb4dfdd3d Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Fri, 16 Jun 2023 17:22:59 +0300 Subject: [PATCH 7/7] making the linter happy --- proxmox/config_sdn_subnet.go | 2 +- proxmox/config_sdn_vnet.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/proxmox/config_sdn_subnet.go b/proxmox/config_sdn_subnet.go index 63bcd9bf..0f9a2a81 100644 --- a/proxmox/config_sdn_subnet.go +++ b/proxmox/config_sdn_subnet.go @@ -75,7 +75,7 @@ func (c *ConfigSDNSubnet) Validate(vnet, id string, create bool, client *Client) return } if !vnetExists { - return fmt.Errorf("Subnet must be created in an existing vnet. Vnet (%s) wasn't found", vnet) + return fmt.Errorf("subnet must be created in an existing vnet. vnet (%s) wasn't found", vnet) } exists, err := client.CheckSDNSubnetExistance(vnet, id) if err != nil { diff --git a/proxmox/config_sdn_vnet.go b/proxmox/config_sdn_vnet.go index 7c6bf72c..f7793226 100644 --- a/proxmox/config_sdn_vnet.go +++ b/proxmox/config_sdn_vnet.go @@ -72,12 +72,12 @@ func (c *ConfigSDNVNet) Validate(id string, create bool, client *Client) (err er return } if !zoneExists { - return fmt.Errorf("VNet must be associated to an existing zone. Zone %s could not be found.", c.Zone) + return fmt.Errorf("vnet must be associated to an existing zone. zone %s could not be found", c.Zone) } if c.Alias != "" { - regex, _ := regexp.Compile(`^(?^i:[\(\)-_.\w\d\s]{0,256})$`) + regex, _ := regexp.Compile(`^(?i:[\(\)-_.\w\d\s]{0,256})$`) if !regex.Match([]byte(c.Alias)) { - return fmt.Errorf(`Alias must match the validation regular expression: ^(?^i:[\(\)-_.\w\d\s]{0,256})$`) + return fmt.Errorf(`alias must match the validation regular expression: ^(?i:[\(\)-_.\w\d\s]{0,256})$`) } } err = ValidateIntGreater(0, c.Tag, "tag")