mirror of
https://codeberg.org/lauralani/ipam.git
synced 2024-11-27 22:00:01 +01:00
Merge pull request 'Add feature to add next free IP' (#11) from 5-add-next-free-ip-automatically into main
Reviewed-on: https://codeberg.org/lauralani/ipam/pulls/11
This commit is contained in:
commit
b0aa4063dd
3 changed files with 106 additions and 35 deletions
|
@ -6,6 +6,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -19,6 +20,21 @@ type Subnet struct {
|
||||||
Addresses []Address `json:"addresses"`
|
Addresses []Address `json:"addresses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIPCount gets the IP count for the Subnet
|
||||||
|
//
|
||||||
|
// Returns the IP count or -1 if it's a IPv6 prefix
|
||||||
|
func (s Subnet) GetIPCount() int {
|
||||||
|
if !s.Subnet.Addr().Is4() {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
hostbits := float64(32 - s.Subnet.Bits())
|
||||||
|
if s.Subnet.Bits() == 31 {
|
||||||
|
return 2
|
||||||
|
} else {
|
||||||
|
return int(math.Pow(2, hostbits)) - 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HasIP checks if a Subnet already contains given netip.Addr.
|
// HasIP checks if a Subnet already contains given netip.Addr.
|
||||||
// Returns true if the IP already is present, false otherwise.
|
// Returns true if the IP already is present, false otherwise.
|
||||||
func (s Subnet) HasIP(ip netip.Addr) bool {
|
func (s Subnet) HasIP(ip netip.Addr) bool {
|
||||||
|
@ -33,6 +49,42 @@ func (s Subnet) HasIP(ip netip.Addr) bool {
|
||||||
return iscontained
|
return iscontained
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindFirstFreeIP finds and returns the next free netip.Addr
|
||||||
|
// or an invalid netip.Addr if no free IP was found
|
||||||
|
func (s Subnet) FindFirstFreeIP() netip.Addr {
|
||||||
|
var ip netip.Addr
|
||||||
|
|
||||||
|
if s.Subnet.Addr().Is4() {
|
||||||
|
subnetips := s.GetIPCount()
|
||||||
|
|
||||||
|
// handling /31 prefixes
|
||||||
|
if subnetips == 2 {
|
||||||
|
ip = s.Subnet.Addr()
|
||||||
|
} else {
|
||||||
|
ip = s.Subnet.Addr().Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// start at first free IP
|
||||||
|
for count := 0; count < subnetips; count++ {
|
||||||
|
|
||||||
|
if s.HasIP(ip) {
|
||||||
|
ip = ip.Next()
|
||||||
|
} else {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip = s.Subnet.Addr().Next()
|
||||||
|
for ; s.Subnet.Contains(ip); ip = ip.Next() {
|
||||||
|
if !s.HasIP(ip) {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveIP removes the Address object for given ip from
|
// RemoveIP removes the Address object for given ip from
|
||||||
// the Address list of the subnet.
|
// the Address list of the subnet.
|
||||||
//
|
//
|
||||||
|
|
|
@ -15,67 +15,80 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var ipaddCmd = &cobra.Command{
|
var ipaddCmd = &cobra.Command{
|
||||||
Use: "add ipaddress [hostname]",
|
Use: "add ipaddress|subnet [hostname]",
|
||||||
Short: "Add new IP address",
|
Short: "Add new IP address",
|
||||||
Long: `Add new IP address`,
|
Long: `Adds a new IP address OR the next free IP address from a subnet`,
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var ipaddress, hostname string
|
var iparg, hostname string
|
||||||
|
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
ipaddress = args[0]
|
iparg = args[0]
|
||||||
hostname = ""
|
hostname = ""
|
||||||
} else {
|
} else {
|
||||||
ipaddress = args[0]
|
iparg = args[0]
|
||||||
hostname = args[1]
|
hostname = args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, parseerr := netip.ParseAddr(ipaddress)
|
var bestsubnet Subnet
|
||||||
|
var ip netip.Addr
|
||||||
|
|
||||||
// Exit if parsed value is no valid IP
|
argip, ipparseerr := netip.ParseAddr(iparg)
|
||||||
if parseerr != nil {
|
argsubnet, subparseerr := netip.ParsePrefix(iparg)
|
||||||
fmt.Println("[ERROR]", parseerr)
|
|
||||||
|
if ipparseerr != nil && subparseerr != nil {
|
||||||
|
fmt.Printf("[ERROR] Argument is neither a valid IP address nor a valid Subnet: %v", iparg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
} else if ipparseerr == nil && subparseerr != nil {
|
||||||
|
// argument was a single IP
|
||||||
|
var subnetexists bool
|
||||||
|
bestsubnet, subnetexists = FindBestSubnet(argip)
|
||||||
|
if !subnetexists {
|
||||||
|
fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", iparg)
|
||||||
|
fmt.Printf("[ERROR] Maybe you need to add it first?\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if bestsubnet.HasIP(argip) {
|
||||||
|
fmt.Printf("[ERROR] IP %v already exists in subnet %v\n", argip.String(), bestsubnet.Subnet.String())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ip = argip
|
||||||
|
|
||||||
// Exit if parsed value is an IPv6 Address
|
} else if subparseerr == nil && ipparseerr != nil {
|
||||||
// TODO: Implement IPv6 support
|
// argument was a subnet
|
||||||
//if !ip.Is4() {
|
var subneterr error
|
||||||
// fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
|
bestsubnet, subneterr = GetSubnet(argsubnet)
|
||||||
// os.Exit(1)
|
if subneterr != nil {
|
||||||
//}
|
fmt.Println("[ERROR]", subneterr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ip = bestsubnet.FindFirstFreeIP()
|
||||||
|
|
||||||
subnet, subnetexists := FindBestSubnet(ip)
|
if !ip.IsValid() {
|
||||||
|
fmt.Printf("[ERROR] Found no free IP in Subnet %v\n", argsubnet.String())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if !subnetexists {
|
|
||||||
fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", ipaddress)
|
|
||||||
fmt.Printf("[ERROR] Maybe you need to add it first?\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if subnet.HasIP(ip) {
|
|
||||||
fmt.Printf("[ERROR] IP %v already exists in subnet %v\n", ip.String(), subnet.Subnet.String())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentuser, _ := user.Current()
|
currentuser, _ := user.Current()
|
||||||
timestamp := time.Now()
|
timestamp := time.Now()
|
||||||
|
|
||||||
subnet.Addresses = append(subnet.Addresses, Address{ip, hostname, timestamp, currentuser.Username})
|
bestsubnet.Addresses = append(bestsubnet.Addresses, Address{ip, hostname, timestamp, currentuser.Username})
|
||||||
subnet.ChangedBy = currentuser.Username
|
bestsubnet.ChangedBy = currentuser.Username
|
||||||
subnet.ChangedAt = timestamp
|
bestsubnet.ChangedAt = timestamp
|
||||||
|
|
||||||
writeerr := subnet.WriteSubnet()
|
writeerr := bestsubnet.WriteSubnet()
|
||||||
if writeerr != nil {
|
if writeerr != nil {
|
||||||
fmt.Println("[ERROR]", writeerr)
|
fmt.Println("[ERROR]", writeerr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
fmt.Printf("added ip:\nip: %v\n", ipaddress)
|
fmt.Printf("added ip:\nip: %v\n", ip.String())
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("added ip:\nip: %v\nhostname: %v\n", ipaddress, hostname)
|
fmt.Printf("added ip:\nip: %v\nhostname: %v\n", ip.String(), hostname)
|
||||||
dnserr := AddDNSFqdn(hostname, ip)
|
dnserr := AddDNSFqdn(hostname, ip)
|
||||||
if dnserr != nil {
|
if dnserr != nil {
|
||||||
fmt.Println("[ERROR]", dnserr)
|
fmt.Println("[ERROR]", dnserr)
|
||||||
|
|
|
@ -114,7 +114,13 @@ func (s Subnet) WriteSubnet() error {
|
||||||
fmt.Println("[ERROR]", fileerr)
|
fmt.Println("[ERROR]", fileerr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func(file *os.File) {
|
||||||
|
err := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[ERROR]", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}(file)
|
||||||
|
|
||||||
_, writeerr := file.Write(data)
|
_, writeerr := file.Write(data)
|
||||||
if writeerr != nil {
|
if writeerr != nil {
|
||||||
|
@ -137,12 +143,12 @@ func GetSubnet(net netip.Prefix) (Subnet, error) {
|
||||||
|
|
||||||
content, readerr := os.ReadFile(filename)
|
content, readerr := os.ReadFile(filename)
|
||||||
if readerr != nil {
|
if readerr != nil {
|
||||||
return Subnet{}, readerr
|
return Subnet{}, fmt.Errorf("can't open file for subnet %v for reading", net.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
marsherr := json.Unmarshal(content, &subnet)
|
marsherr := json.Unmarshal(content, &subnet)
|
||||||
if marsherr != nil {
|
if marsherr != nil {
|
||||||
return Subnet{}, marsherr
|
return Subnet{}, fmt.Errorf("can't unmarshal file contents of file %v\n%v", filename, marsherr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return subnet, nil
|
return subnet, nil
|
||||||
|
|
Loading…
Reference in a new issue