fix whitespace stuff

This commit is contained in:
Adora Laura Kalb 2023-03-25 15:16:26 +01:00
parent 6d75376d7f
commit 67f9e0b6fb
19 changed files with 1059 additions and 1059 deletions

View file

@ -5,32 +5,32 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"errors" "errors"
"net/netip" "net/netip"
"time" "time"
) )
type Subnet struct { type Subnet struct {
Subnet netip.Prefix `json:"subnet"` Subnet netip.Prefix `json:"subnet"`
Name string `json:"name"` Name string `json:"name"`
Vlan string `json:"vlan"` Vlan string `json:"vlan"`
ChangedAt time.Time `json:"changedat,omitempty"` ChangedAt time.Time `json:"changedat,omitempty"`
ChangedBy string `json:"changedby,omitempty"` ChangedBy string `json:"changedby,omitempty"`
Addresses []Address `json:"addresses"` Addresses []Address `json:"addresses"`
} }
// 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 {
iscontained := false iscontained := false
for _, element := range s.Addresses { for _, element := range s.Addresses {
if element.IP.Compare(ip) == 0 { if element.IP.Compare(ip) == 0 {
iscontained = true iscontained = true
} }
} }
return iscontained return iscontained
} }
// RemoveIP removes the Address object for given ip from // RemoveIP removes the Address object for given ip from
@ -40,20 +40,20 @@ func (s Subnet) HasIP(ip netip.Addr) bool {
// successful, or an empty Subnet and an error if // successful, or an empty Subnet and an error if
// ip could not be deleted. // ip could not be deleted.
func (s Subnet) RemoveIP(ip netip.Addr) (Subnet, error) { func (s Subnet) RemoveIP(ip netip.Addr) (Subnet, error) {
var addrlist []Address var addrlist []Address
if !s.HasIP(ip) { if !s.HasIP(ip) {
return Subnet{}, errors.New("IP " + ip.String() + " wasn't found in subnet " + s.Subnet.String()) return Subnet{}, errors.New("IP " + ip.String() + " wasn't found in subnet " + s.Subnet.String())
} }
for _, item := range s.Addresses { for _, item := range s.Addresses {
if item.IP.Compare(ip) != 0 { if item.IP.Compare(ip) != 0 {
addrlist = append(addrlist, item) addrlist = append(addrlist, item)
} }
} }
s.Addresses = addrlist s.Addresses = addrlist
return s, nil return s, nil
} }
// GetIP returns the Address object for the subnet with // GetIP returns the Address object for the subnet with
@ -62,18 +62,18 @@ func (s Subnet) RemoveIP(ip netip.Addr) (Subnet, error) {
// Returns the Address object and true if a corresponding // Returns the Address object and true if a corresponding
// object was found, an empty Address and false otherwise. // object was found, an empty Address and false otherwise.
func (s Subnet) GetIP(ip netip.Addr) (Address, bool) { func (s Subnet) GetIP(ip netip.Addr) (Address, bool) {
for _, item := range s.Addresses { for _, item := range s.Addresses {
if item.IP.Compare(ip) == 0 { if item.IP.Compare(ip) == 0 {
return item, true return item, true
} }
} }
return Address{}, false return Address{}, false
} }
type Address struct { type Address struct {
IP netip.Addr `json:"ip"` IP netip.Addr `json:"ip"`
FQDN string `json:"fqdn"` FQDN string `json:"fqdn"`
ChangedAt time.Time `json:"changedat,omitempty"` ChangedAt time.Time `json:"changedat,omitempty"`
ChangedBy string `json:"changedby,omitempty"` ChangedBy string `json:"changedby,omitempty"`
} }

View file

@ -5,5 +5,5 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
const ( const (
ipam_version = "DEVEL" ipam_version = "DEVEL"
) )

View file

@ -5,92 +5,92 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"path" "path"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// exportCmd represents the export command // exportCmd represents the export command
var exportCmd = &cobra.Command{ var exportCmd = &cobra.Command{
Use: "export", Use: "export",
Short: "Export current ipam configuration (not implemented)", Short: "Export current ipam configuration (not implemented)",
Long: `Export current ipam contents to importable data format. Long: `Export current ipam contents to importable data format.
You can either export a single subnet or all subnets.`, You can either export a single subnet or all subnets.`,
Example: "ipam export\nipam export 192.168.0.0/24", Example: "ipam export\nipam export 192.168.0.0/24",
Args: cobra.RangeArgs(0, 1), Args: cobra.RangeArgs(0, 1),
Aliases: []string{"ex"}, Aliases: []string{"ex"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var jsonoutput []Subnet var jsonoutput []Subnet
if len(args) == 1 { if len(args) == 1 {
prefix, parseerr := netip.ParsePrefix(args[0]) prefix, parseerr := netip.ParsePrefix(args[0])
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
subnet, geterr := GetSubnet(prefix) subnet, geterr := GetSubnet(prefix)
if geterr != nil { if geterr != nil {
fmt.Println("[ERROR]", geterr) fmt.Println("[ERROR]", geterr)
os.Exit(1) os.Exit(1)
} }
jsonoutput = append(jsonoutput, subnet) jsonoutput = append(jsonoutput, subnet)
} else { } else {
subnetlist := ListSubnets() subnetlist := ListSubnets()
for _, net := range subnetlist { for _, net := range subnetlist {
prefix, parseerr := netip.ParsePrefix(net) prefix, parseerr := netip.ParsePrefix(net)
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
subnet, geterr := GetSubnet(prefix) subnet, geterr := GetSubnet(prefix)
if geterr != nil { if geterr != nil {
fmt.Println("[ERROR]", geterr) fmt.Println("[ERROR]", geterr)
os.Exit(1) os.Exit(1)
} }
jsonoutput = append(jsonoutput, subnet) jsonoutput = append(jsonoutput, subnet)
} }
} }
//workingdir, _ := os.Getwd() //workingdir, _ := os.Getwd()
//timestamp := time.Now().Format("2006-01-02_15-04") //timestamp := time.Now().Format("2006-01-02_15-04")
//exportfilename := workingdir + "/ipam_export_" + timestamp + ".json" //exportfilename := workingdir + "/ipam_export_" + timestamp + ".json"
var exportname string var exportname string
flagpath, _ := cmd.Flags().GetString("file") flagpath, _ := cmd.Flags().GetString("file")
if path.IsAbs(flagpath) { if path.IsAbs(flagpath) {
exportname = flagpath exportname = flagpath
} else { } else {
wd, _ := os.Getwd() wd, _ := os.Getwd()
exportname = path.Join(wd, flagpath) exportname = path.Join(wd, flagpath)
} }
data, _ := json.MarshalIndent(jsonoutput, "", " ") data, _ := json.MarshalIndent(jsonoutput, "", " ")
file, fileerr := os.Create(exportname) file, fileerr := os.Create(exportname)
if fileerr != nil { if fileerr != nil {
fmt.Println("[ERROR]", fileerr) fmt.Println("[ERROR]", fileerr)
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer file.Close()
_, writeerr := file.Write(data) _, writeerr := file.Write(data)
if writeerr != nil { if writeerr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("[INFO] Data was exported to file %v\n", exportname) fmt.Printf("[INFO] Data was exported to file %v\n", exportname)
}, },
} }
func init() { func init() {
rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(exportCmd)
timestamp := time.Now().Format("2006-01-02_15-04") timestamp := time.Now().Format("2006-01-02_15-04")
exportCmd.Flags().StringP("file", "f", "./ipam_export_"+timestamp+".json", "File name for exported data.\nCan be both absolute or relative path.") exportCmd.Flags().StringP("file", "f", "./ipam_export_"+timestamp+".json", "File name for exported data.\nCan be both absolute or relative path.")
} }

View file

@ -1,9 +1,9 @@
package cmd package cmd
func reverse(elements []string) []string { func reverse(elements []string) []string {
for i := 0; i < len(elements)/2; i++ { for i := 0; i < len(elements)/2; i++ {
j := len(elements) - i - 1 j := len(elements) - i - 1
elements[i], elements[j] = elements[j], elements[i] elements[i], elements[j] = elements[j], elements[i]
} }
return elements return elements
} }

View file

@ -5,72 +5,72 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"path" "path"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var importCmd = &cobra.Command{ var importCmd = &cobra.Command{
Use: "import", Use: "import",
Short: "Import ipam configuration (not implemented)", Short: "Import ipam configuration (not implemented)",
Long: `Import subnets to ipam.`, Long: `Import subnets to ipam.`,
Example: "ipam import --file import.json", Example: "ipam import --file import.json",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Aliases: []string{"im"}, Aliases: []string{"im"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var importname string var importname string
var subnets []Subnet var subnets []Subnet
flagpath, _ := cmd.Flags().GetString("file") flagpath, _ := cmd.Flags().GetString("file")
if path.IsAbs(flagpath) { if path.IsAbs(flagpath) {
importname = flagpath importname = flagpath
} else { } else {
wd, _ := os.Getwd() wd, _ := os.Getwd()
importname = path.Join(wd, flagpath) importname = path.Join(wd, flagpath)
} }
file, readerr := os.ReadFile(importname) file, readerr := os.ReadFile(importname)
if readerr != nil { if readerr != nil {
fmt.Printf("[ERROR] Can't read file %v\n", importname) fmt.Printf("[ERROR] Can't read file %v\n", importname)
fmt.Println(readerr) fmt.Println(readerr)
} }
marsherr := json.Unmarshal(file, &subnets) marsherr := json.Unmarshal(file, &subnets)
if marsherr != nil { if marsherr != nil {
fmt.Printf("[ERROR] Invalid format for file %v\n", importname) fmt.Printf("[ERROR] Invalid format for file %v\n", importname)
fmt.Println(marsherr) fmt.Println(marsherr)
} }
for _, subnet := range subnets { for _, subnet := range subnets {
fmt.Printf("[INFO] Start import of %v\n", subnet.Subnet.String()) fmt.Printf("[INFO] Start import of %v\n", subnet.Subnet.String())
subnet.ChangedBy = "ipam import" subnet.ChangedBy = "ipam import"
subnet.ChangedAt = time.Now() subnet.ChangedAt = time.Now()
for _, addr := range subnet.Addresses { for _, addr := range subnet.Addresses {
addr.ChangedBy = "ipam import" addr.ChangedBy = "ipam import"
addr.ChangedAt = time.Now() addr.ChangedAt = time.Now()
if addr.FQDN != "" { if addr.FQDN != "" {
AddDNSFqdn(addr.FQDN, addr.IP) AddDNSFqdn(addr.FQDN, addr.IP)
} }
} }
suberr := subnet.WriteSubnet() suberr := subnet.WriteSubnet()
if suberr != nil { if suberr != nil {
fmt.Printf("[ERROR] Can't write subnet to file %v\n", subnet.Subnet.String()) fmt.Printf("[ERROR] Can't write subnet to file %v\n", subnet.Subnet.String())
fmt.Println(suberr) fmt.Println(suberr)
} }
fmt.Printf("[INFO] Imported subnet %v successfully\n", subnet.Subnet.String()) fmt.Printf("[INFO] Imported subnet %v successfully\n", subnet.Subnet.String())
} }
}, },
} }
func init() { func init() {
rootCmd.AddCommand(importCmd) rootCmd.AddCommand(importCmd)
importCmd.Flags().StringP("file", "f", "import.json", "File to use for import operation") importCmd.Flags().StringP("file", "f", "import.json", "File to use for import operation")
} }

View file

@ -5,92 +5,92 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"os/user" "os/user"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ipaddCmd = &cobra.Command{ var ipaddCmd = &cobra.Command{
Use: "add ipaddress [hostname]", Use: "add ipaddress [hostname]",
Short: "Add new IP address", Short: "Add new IP address",
Long: `Add new IP address`, Long: `Add new IP address`,
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 ipaddress, hostname string
if len(args) == 1 { if len(args) == 1 {
ipaddress = args[0] ipaddress = args[0]
hostname = "" hostname = ""
} else { } else {
ipaddress = args[0] ipaddress = args[0]
hostname = args[1] hostname = args[1]
} }
ip, parseerr := netip.ParseAddr(ipaddress) ip, parseerr := netip.ParseAddr(ipaddress)
// Exit if parsed value is no valid IP // Exit if parsed value is no valid IP
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
// Exit if parsed value is an IPv6 Address // Exit if parsed value is an IPv6 Address
// TODO: Implement IPv6 support // TODO: Implement IPv6 support
if !ip.Is4() { if !ip.Is4() {
fmt.Printf("[ERROR] IPv6 is not yet supported!\n") fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
os.Exit(1) os.Exit(1)
} }
subnet, subnetexists := FindBestSubnet(ip) subnet, subnetexists := FindBestSubnet(ip)
if !subnetexists { if !subnetexists {
fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", ipaddress) fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", ipaddress)
fmt.Printf("[ERROR] Maybe you need to add it first?\n") fmt.Printf("[ERROR] Maybe you need to add it first?\n")
os.Exit(1) os.Exit(1)
} }
if subnet.HasIP(ip) { if subnet.HasIP(ip) {
fmt.Printf("[ERROR] IP %v already exists in subnet %v\n", ip.String(), subnet.Subnet.String()) fmt.Printf("[ERROR] IP %v already exists in subnet %v\n", ip.String(), subnet.Subnet.String())
os.Exit(1) os.Exit(1)
} }
currentuser, _ := user.Current() currentuser, _ := user.Current()
subnet.Addresses = append(subnet.Addresses, Address{ip, hostname, time.Now(), currentuser.Username}) subnet.Addresses = append(subnet.Addresses, Address{ip, hostname, time.Now(), currentuser.Username})
writeerr := subnet.WriteSubnet() writeerr := subnet.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", ipaddress)
} else { } else {
fmt.Printf("added ip:\nip: %v\nhostname: %v\n", ipaddress, hostname) fmt.Printf("added ip:\nip: %v\nhostname: %v\n", ipaddress, hostname)
dnserr := AddDNSFqdn(hostname, ip) dnserr := AddDNSFqdn(hostname, ip)
if dnserr != nil { if dnserr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
} }
}, },
} }
func init() { func init() {
ipCmd.AddCommand(ipaddCmd) ipCmd.AddCommand(ipaddCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// addCmd.PersistentFlags().String("foo", "", "A help for foo") // addCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -4,72 +4,72 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// deleteCmd represents the delete command // deleteCmd represents the delete command
var ipdeleteCmd = &cobra.Command{ var ipdeleteCmd = &cobra.Command{
Use: "delete ipaddress", Use: "delete ipaddress",
Short: "Delete an IP address", Short: "Delete an IP address",
Long: `Delete an IP address`, Long: `Delete an IP address`,
Aliases: []string{"d"}, Aliases: []string{"d"},
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Example: "ipam ip delete 192.168.0.1", Example: "ipam ip delete 192.168.0.1",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ip, parseerr := netip.ParseAddr(args[0]) ip, parseerr := netip.ParseAddr(args[0])
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
subnet, subnetexists := FindBestSubnet(ip) subnet, subnetexists := FindBestSubnet(ip)
if !subnetexists { if !subnetexists {
fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String()) fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String())
os.Exit(1) os.Exit(1)
} }
address, _ := subnet.GetIP(ip) address, _ := subnet.GetIP(ip)
subnet, removeerr := subnet.RemoveIP(ip) subnet, removeerr := subnet.RemoveIP(ip)
if removeerr != nil { if removeerr != nil {
fmt.Println("[ERROR]", removeerr) fmt.Println("[ERROR]", removeerr)
os.Exit(1) os.Exit(1)
} }
writeerr := subnet.WriteSubnet() writeerr := subnet.WriteSubnet()
if writeerr != nil { if writeerr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
if address.FQDN == "" { if address.FQDN == "" {
fmt.Printf("deleted ip %v\n", address.IP.String()) fmt.Printf("deleted ip %v\n", address.IP.String())
} else { } else {
fmt.Printf("deleted ip %v (%v)\n", address.IP.String(), address.FQDN) fmt.Printf("deleted ip %v (%v)\n", address.IP.String(), address.FQDN)
dnserr := DeleteDNSFqdn(address.FQDN, address.IP) dnserr := DeleteDNSFqdn(address.FQDN, address.IP)
if dnserr != nil { if dnserr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
} }
}, },
} }
func init() { func init() {
ipCmd.AddCommand(ipdeleteCmd) ipCmd.AddCommand(ipdeleteCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// deleteCmd.PersistentFlags().String("foo", "", "A help for foo") // deleteCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -4,32 +4,32 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ipeditCmd = &cobra.Command{ var ipeditCmd = &cobra.Command{
Use: "edit", Use: "edit",
Short: "Edit an IP address", Short: "Edit an IP address",
Long: `Edit an IP address`, Long: `Edit an IP address`,
Aliases: []string{"e"}, Aliases: []string{"e"},
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("not implemented yet; please delete and readd") fmt.Println("not implemented yet; please delete and readd")
}, },
} }
func init() { func init() {
ipCmd.AddCommand(ipeditCmd) ipCmd.AddCommand(ipeditCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// editCmd.PersistentFlags().String("foo", "", "A help for foo") // editCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// editCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // editCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -4,57 +4,57 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// showCmd represents the show command // showCmd represents the show command
var ipshowCmd = &cobra.Command{ var ipshowCmd = &cobra.Command{
Use: "show", Use: "show",
Short: "Show IP and associated name", Short: "Show IP and associated name",
Long: `Show IP and associated name`, Long: `Show IP and associated name`,
Aliases: []string{"s"}, Aliases: []string{"s"},
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Example: "ipam ip show 192.168.0.1", Example: "ipam ip show 192.168.0.1",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ip, parseerr := netip.ParseAddr(args[0]) ip, parseerr := netip.ParseAddr(args[0])
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
subnet, subnetexists := FindBestSubnet(ip) subnet, subnetexists := FindBestSubnet(ip)
if !subnetexists { if !subnetexists {
fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String()) fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String())
os.Exit(1) os.Exit(1)
} }
addr, addrexists := subnet.GetIP(ip) addr, addrexists := subnet.GetIP(ip)
if !addrexists { if !addrexists {
fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String()) fmt.Printf("[ERROR] Couldn't find IP %v\n", ip.String())
os.Exit(1) os.Exit(1)
} }
fmt.Printf("IP: %v\n", ip.String()) fmt.Printf("IP: %v\n", ip.String())
fmt.Printf("FQDN: %v\n", addr.FQDN) fmt.Printf("FQDN: %v\n", addr.FQDN)
fmt.Printf("Subnet: %v (%v)\n", subnet.Subnet.String(), subnet.Name) fmt.Printf("Subnet: %v (%v)\n", subnet.Subnet.String(), subnet.Name)
}, },
} }
func init() { func init() {
ipCmd.AddCommand(ipshowCmd) ipCmd.AddCommand(ipshowCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// showCmd.PersistentFlags().String("foo", "", "A help for foo") // showCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -5,27 +5,27 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// ipCmd represents the ip command // ipCmd represents the ip command
var ipCmd = &cobra.Command{ var ipCmd = &cobra.Command{
Use: "ip", Use: "ip",
Short: "manage ip addresses", Short: "manage ip addresses",
Long: `Add, delete, edit and show IP addresses`, Long: `Add, delete, edit and show IP addresses`,
Aliases: []string{"i"}, Aliases: []string{"i"},
} }
func init() { func init() {
rootCmd.AddCommand(ipCmd) rootCmd.AddCommand(ipCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// ipCmd.PersistentFlags().String("foo", "", "A help for foo") // ipCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// ipCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // ipCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -5,32 +5,32 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/netip" "net/netip"
"strings" "strings"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type DNSZone struct { type DNSZone struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Kind string `json:"kind"` Kind string `json:"kind"`
RRsets []DNSRecordSet `json:"rrsets"` RRsets []DNSRecordSet `json:"rrsets"`
Metadata map[string]string `json:"metadata"` Metadata map[string]string `json:"metadata"`
DNSSEC bool `json:"dnssec"` DNSSEC bool `json:"dnssec"`
NSEC3Param string `json:"nsec3param,omitempty"` NSEC3Param string `json:"nsec3param,omitempty"`
Account string `json:"account,omitempty"` Account string `json:"account,omitempty"`
Serial int `json:"serial"` Serial int `json:"serial"`
} }
type Patch struct { type Patch struct {
Rrsets []DNSRecordSet `json:"rrsets"` Rrsets []DNSRecordSet `json:"rrsets"`
} }
// Checks if a given Record already exists in the DNSRecordSet list. // Checks if a given Record already exists in the DNSRecordSet list.
@ -38,106 +38,106 @@ type Patch struct {
// Returns the DNSRecordSet and true if record exists, empty // Returns the DNSRecordSet and true if record exists, empty
// DNSRecordSet and false if not. // DNSRecordSet and false if not.
func (z DNSZone) GetRecord(fqdn string, rtype string, rcontent string) (DNSRecordSet, bool) { func (z DNSZone) GetRecord(fqdn string, rtype string, rcontent string) (DNSRecordSet, bool) {
if !strings.HasSuffix(fqdn, ".") { if !strings.HasSuffix(fqdn, ".") {
fqdn = fqdn + "." fqdn = fqdn + "."
} }
if (rtype == "PTR") && !strings.HasSuffix(rcontent, ".") { if (rtype == "PTR") && !strings.HasSuffix(rcontent, ".") {
rcontent = rcontent + "." rcontent = rcontent + "."
} }
for _, recordset := range z.RRsets { for _, recordset := range z.RRsets {
if recordset.Name == fqdn && recordset.Type == rtype { if recordset.Name == fqdn && recordset.Type == rtype {
for _, record := range recordset.Records { for _, record := range recordset.Records {
if record.Content == rcontent { if record.Content == rcontent {
return recordset, true return recordset, true
} }
} }
} }
} }
return DNSRecordSet{}, false return DNSRecordSet{}, false
} }
// Sends a PATCH API request for DNSZone z. Returns error or nil // Sends a PATCH API request for DNSZone z. Returns error or nil
// //
// Example args for "test.example.com IN A 127.0.0.1" // Example args for "test.example.com IN A 127.0.0.1"
// //
// z.Name = "example.com." // z.Name = "example.com."
// record = "test" // record = "test"
// value = "127.0.0.1" // value = "127.0.0.1"
// recordtype = "A" // recordtype = "A"
// changetype = "REPLACE" // changetype = "REPLACE"
func (z DNSZone) SendPATCH(record string, value string, recordtype string, changetype string) error { func (z DNSZone) SendPATCH(record string, value string, recordtype string, changetype string) error {
pdnsendpoint := viper.GetString("powerdnsendpoint") pdnsendpoint := viper.GetString("powerdnsendpoint")
pdnsapikey := viper.GetString("powerdnsapikey") pdnsapikey := viper.GetString("powerdnsapikey")
debug, _ := rootCmd.Flags().GetBool("debug") debug, _ := rootCmd.Flags().GetBool("debug")
if !viper.GetBool("powerdnsenabled") { if !viper.GetBool("powerdnsenabled") {
return nil return nil
} }
url := pdnsendpoint + "/api/v1/servers/localhost/zones/" + z.Name url := pdnsendpoint + "/api/v1/servers/localhost/zones/" + z.Name
if debug { if debug {
fmt.Println("[DEBUG] PowerDNS URL: " + url) fmt.Println("[DEBUG] PowerDNS URL: " + url)
} }
rset := DNSRecordSet{} rset := DNSRecordSet{}
rset.Changetype = changetype rset.Changetype = changetype
rset.Name = strings.Join([]string{record, z.Name}, ".") rset.Name = strings.Join([]string{record, z.Name}, ".")
rset.TTL = 3600 rset.TTL = 3600
rset.Type = recordtype rset.Type = recordtype
rec := DNSRecord{} rec := DNSRecord{}
if recordtype == "PTR" { if recordtype == "PTR" {
rec.Content = value + "." rec.Content = value + "."
} else { } else {
rec.Content = value rec.Content = value
} }
rset.Records = append(rset.Records, rec) rset.Records = append(rset.Records, rec)
patch := Patch{} patch := Patch{}
patch.Rrsets = append(patch.Rrsets, rset) patch.Rrsets = append(patch.Rrsets, rset)
payload, marsherr := json.Marshal(patch) payload, marsherr := json.Marshal(patch)
if marsherr != nil { if marsherr != nil {
return marsherr return marsherr
} }
req, reqerr := http.NewRequest("PATCH", url, bytes.NewBuffer(payload)) req, reqerr := http.NewRequest("PATCH", url, bytes.NewBuffer(payload))
if reqerr != nil { if reqerr != nil {
return reqerr return reqerr
} }
req.Header.Add("X-API-Key", pdnsapikey) req.Header.Add("X-API-Key", pdnsapikey)
req.Header.Add("Content-Type", "application/json") req.Header.Add("Content-Type", "application/json")
client := &http.Client{} client := &http.Client{}
resp, resperr := client.Do(req) resp, resperr := client.Do(req)
if resperr != nil { if resperr != nil {
return resperr return resperr
} }
if resp.StatusCode != 204 { if resp.StatusCode != 204 {
defer resp.Body.Close() defer resp.Body.Close()
body, readerr := io.ReadAll(resp.Body) body, readerr := io.ReadAll(resp.Body)
if readerr != nil { if readerr != nil {
fmt.Println(readerr) fmt.Println(readerr)
} }
return errors.New("HTTP Error: " + resp.Status + "\n" + string(body)) return errors.New("HTTP Error: " + resp.Status + "\n" + string(body))
} }
return nil return nil
} }
type DNSRecordSet struct { type DNSRecordSet struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
TTL int `json:"ttl"` TTL int `json:"ttl"`
Records []DNSRecord `json:"records"` Records []DNSRecord `json:"records"`
Changetype string `json:"changetype,omitempty"` Changetype string `json:"changetype,omitempty"`
} }
type DNSRecord struct { type DNSRecord struct {
Content string `json:"content"` Content string `json:"content"`
Disabled bool `json:"disabled,omitempty"` Disabled bool `json:"disabled,omitempty"`
SetPTR bool `json:"set-ptr,omitempty"` SetPTR bool `json:"set-ptr,omitempty"`
} }
// GetDNSZone retrieves the corresponding DNSZone for string zone // GetDNSZone retrieves the corresponding DNSZone for string zone
@ -146,45 +146,45 @@ type DNSRecord struct {
// Returns the DNSZone and nil if successful, empty DNSZone and an // Returns the DNSZone and nil if successful, empty DNSZone and an
// error otherwise // error otherwise
func GetDNSZone(zone string) (DNSZone, error) { func GetDNSZone(zone string) (DNSZone, error) {
if !strings.HasSuffix(zone, ".") { if !strings.HasSuffix(zone, ".") {
zone = zone + "." zone = zone + "."
} }
pdnsendpoint := viper.GetString("powerdnsendpoint") pdnsendpoint := viper.GetString("powerdnsendpoint")
pdnsapikey := viper.GetString("powerdnsapikey") pdnsapikey := viper.GetString("powerdnsapikey")
url := pdnsendpoint + "/api/v1/servers/localhost/zones/" + zone url := pdnsendpoint + "/api/v1/servers/localhost/zones/" + zone
req, reqerr := http.NewRequest("GET", url, nil) req, reqerr := http.NewRequest("GET", url, nil)
if reqerr != nil { if reqerr != nil {
fmt.Println(reqerr) fmt.Println(reqerr)
return DNSZone{}, reqerr return DNSZone{}, reqerr
} }
req.Header.Add("X-API-Key", pdnsapikey) req.Header.Add("X-API-Key", pdnsapikey)
req.Header.Add("Accept", "application/json") req.Header.Add("Accept", "application/json")
client := &http.Client{} client := &http.Client{}
resp, resperr := client.Do(req) resp, resperr := client.Do(req)
if resperr != nil { if resperr != nil {
fmt.Println(resperr) fmt.Println(resperr)
return DNSZone{}, resperr return DNSZone{}, resperr
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readerr := io.ReadAll(resp.Body) body, readerr := io.ReadAll(resp.Body)
if readerr != nil { if readerr != nil {
fmt.Println(readerr) fmt.Println(readerr)
return DNSZone{}, readerr return DNSZone{}, readerr
} }
var zoneobj DNSZone var zoneobj DNSZone
marsherr := json.Unmarshal(body, &zoneobj) marsherr := json.Unmarshal(body, &zoneobj)
if marsherr != nil { if marsherr != nil {
fmt.Println(marsherr) fmt.Println(marsherr)
return DNSZone{}, marsherr return DNSZone{}, marsherr
} }
return zoneobj, nil return zoneobj, nil
} }
// GetBestDNSZone requests a list of all zones from PowerDNS // GetBestDNSZone requests a list of all zones from PowerDNS
@ -193,70 +193,70 @@ func GetDNSZone(zone string) (DNSZone, error) {
// Returns the found DNSZone and nil if a suitable zone was // Returns the found DNSZone and nil if a suitable zone was
// found, an empty DNSZone object and an error if not. // found, an empty DNSZone object and an error if not.
func GetBestDNSZone(fqdn string) (DNSZone, error) { func GetBestDNSZone(fqdn string) (DNSZone, error) {
pdnsendpoint := viper.GetString("powerdnsendpoint") pdnsendpoint := viper.GetString("powerdnsendpoint")
pdnsapikey := viper.GetString("powerdnsapikey") pdnsapikey := viper.GetString("powerdnsapikey")
fqdn = fqdn + "." fqdn = fqdn + "."
if !viper.GetBool("powerdnsenabled") { if !viper.GetBool("powerdnsenabled") {
return DNSZone{}, errors.New("PowerDNS integration not enabled") return DNSZone{}, errors.New("PowerDNS integration not enabled")
} }
url := pdnsendpoint + "/api/v1/servers/localhost/zones" url := pdnsendpoint + "/api/v1/servers/localhost/zones"
req, reqerr := http.NewRequest("GET", url, nil) req, reqerr := http.NewRequest("GET", url, nil)
if reqerr != nil { if reqerr != nil {
fmt.Println(reqerr) fmt.Println(reqerr)
return DNSZone{}, reqerr return DNSZone{}, reqerr
} }
req.Header.Add("X-API-Key", pdnsapikey) req.Header.Add("X-API-Key", pdnsapikey)
client := &http.Client{} client := &http.Client{}
resp, resperr := client.Do(req) resp, resperr := client.Do(req)
if resperr != nil { if resperr != nil {
fmt.Println(resperr) fmt.Println(resperr)
return DNSZone{}, resperr return DNSZone{}, resperr
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readerr := io.ReadAll(resp.Body) body, readerr := io.ReadAll(resp.Body)
if readerr != nil { if readerr != nil {
fmt.Println(readerr) fmt.Println(readerr)
return DNSZone{}, readerr return DNSZone{}, readerr
} }
var zones []DNSZone var zones []DNSZone
marsherr := json.Unmarshal(body, &zones) marsherr := json.Unmarshal(body, &zones)
if marsherr != nil { if marsherr != nil {
fmt.Println(marsherr) fmt.Println(marsherr)
return DNSZone{}, marsherr return DNSZone{}, marsherr
} }
var bestmatch DNSZone var bestmatch DNSZone
var matchfound bool = false var matchfound bool = false
for _, zone := range zones { for _, zone := range zones {
if strings.HasSuffix(fqdn, zone.Name) { if strings.HasSuffix(fqdn, zone.Name) {
if !matchfound { if !matchfound {
matchfound = true matchfound = true
bestmatch = zone bestmatch = zone
} }
if matchfound && len(zone.Name) > len(bestmatch.Name) { if matchfound && len(zone.Name) > len(bestmatch.Name) {
bestmatch = zone bestmatch = zone
} }
} }
} }
if !matchfound { if !matchfound {
return DNSZone{}, errors.New("No suitable zone found for " + fqdn) return DNSZone{}, errors.New("No suitable zone found for " + fqdn)
} }
zone, geterr := GetDNSZone(bestmatch.ID) zone, geterr := GetDNSZone(bestmatch.ID)
if geterr != nil { if geterr != nil {
return DNSZone{}, geterr return DNSZone{}, geterr
} }
return zone, nil return zone, nil
} }
// AddDNSfqdn tries to create forward and reverse lookup records // AddDNSfqdn tries to create forward and reverse lookup records
@ -265,74 +265,74 @@ func GetBestDNSZone(fqdn string) (DNSZone, error) {
// //
// Returns nil on success, error otherwise // Returns nil on success, error otherwise
func AddDNSFqdn(fqdn string, addr netip.Addr) error { func AddDNSFqdn(fqdn string, addr netip.Addr) error {
debug, _ := rootCmd.Flags().GetBool("debug") debug, _ := rootCmd.Flags().GetBool("debug")
if !viper.GetBool("powerdnsenabled") { if !viper.GetBool("powerdnsenabled") {
if debug { if debug {
fmt.Println("[INFO] PowerDNS integration disabled, skipping DNS operations.") fmt.Println("[INFO] PowerDNS integration disabled, skipping DNS operations.")
} }
return nil return nil
} }
var recordtype string var recordtype string
if addr.Is4() { if addr.Is4() {
recordtype = "A" recordtype = "A"
} else if addr.Is6() { } else if addr.Is6() {
recordtype = "AAAA" recordtype = "AAAA"
} else { } else {
return errors.New(addr.String() + " is not a valid IP address") return errors.New(addr.String() + " is not a valid IP address")
} }
fzone, fzoneerr := GetBestDNSZone(fqdn) fzone, fzoneerr := GetBestDNSZone(fqdn)
if fzoneerr != nil { if fzoneerr != nil {
fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS op\n", fqdn) fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS op\n", fqdn)
} else { } else {
_, frecordexists := fzone.GetRecord(fqdn, recordtype, addr.String()) _, frecordexists := fzone.GetRecord(fqdn, recordtype, addr.String())
if frecordexists { if frecordexists {
fmt.Printf("[DNS] DNS Record for %v already exists, no need to change DNS.\n", fqdn) fmt.Printf("[DNS] DNS Record for %v already exists, no need to change DNS.\n", fqdn)
} else { } else {
fpatcherr := fzone.SendPATCH(strings.Replace(fqdn+".", "."+fzone.Name, "", 1), addr.String(), recordtype, "REPLACE") fpatcherr := fzone.SendPATCH(strings.Replace(fqdn+".", "."+fzone.Name, "", 1), addr.String(), recordtype, "REPLACE")
if fpatcherr != nil { if fpatcherr != nil {
return fpatcherr return fpatcherr
} }
fmt.Printf("[DNS] + %v IN %v %v\n", fqdn, recordtype, addr.String()) fmt.Printf("[DNS] + %v IN %v %v\n", fqdn, recordtype, addr.String())
} }
} }
baseip := addr.StringExpanded() baseip := addr.StringExpanded()
var rfqdn string var rfqdn string
if addr.Is4() { if addr.Is4() {
a := strings.Split(baseip, ".") a := strings.Split(baseip, ".")
b := reverse(a) b := reverse(a)
rfqdn = strings.Join(b, ".") + ".in-addr.arpa" rfqdn = strings.Join(b, ".") + ".in-addr.arpa"
} else if addr.Is6() { } else if addr.Is6() {
a := strings.Replace(baseip, ":", "", -1) a := strings.Replace(baseip, ":", "", -1)
b := strings.Split(a, "") b := strings.Split(a, "")
c := reverse(b) c := reverse(b)
rfqdn = strings.Join(c, ".") + ".ip6.arpa" rfqdn = strings.Join(c, ".") + ".ip6.arpa"
} }
rzone, rzoneerr := GetBestDNSZone(rfqdn) rzone, rzoneerr := GetBestDNSZone(rfqdn)
if rzoneerr != nil { if rzoneerr != nil {
fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS op\n", rfqdn) fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS op\n", rfqdn)
} else { } else {
_, rrecordexists := rzone.GetRecord(rfqdn, "PTR", fqdn) _, rrecordexists := rzone.GetRecord(rfqdn, "PTR", fqdn)
rhost := strings.Replace(rfqdn+".", "."+rzone.Name, "", 1) rhost := strings.Replace(rfqdn+".", "."+rzone.Name, "", 1)
if rrecordexists { if rrecordexists {
fmt.Printf("[DNS] Reverse DNS Record for %v already exists, no need to change DNS.\n", addr.String()) fmt.Printf("[DNS] Reverse DNS Record for %v already exists, no need to change DNS.\n", addr.String())
} else { } else {
rpatcherr := rzone.SendPATCH(rhost, fqdn, "PTR", "REPLACE") rpatcherr := rzone.SendPATCH(rhost, fqdn, "PTR", "REPLACE")
if rpatcherr != nil { if rpatcherr != nil {
return rpatcherr return rpatcherr
} }
fmt.Printf("[DNS] + %v IN %v %v\n", rfqdn, "PTR", fqdn) fmt.Printf("[DNS] + %v IN %v %v\n", rfqdn, "PTR", fqdn)
} }
} }
return nil return nil
} }
// DeleteDNSFqdn tries to delete the corresponding record for // DeleteDNSFqdn tries to delete the corresponding record for
@ -340,70 +340,70 @@ func AddDNSFqdn(fqdn string, addr netip.Addr) error {
// //
// Returns nil on success, error otherwise // Returns nil on success, error otherwise
func DeleteDNSFqdn(fqdn string, addr netip.Addr) error { func DeleteDNSFqdn(fqdn string, addr netip.Addr) error {
debug, _ := rootCmd.Flags().GetBool("debug") debug, _ := rootCmd.Flags().GetBool("debug")
if !viper.GetBool("powerdnsenabled") { if !viper.GetBool("powerdnsenabled") {
if debug { if debug {
fmt.Println("[INFO] PowerDNS integration disabled, skipping DNS operations.") fmt.Println("[INFO] PowerDNS integration disabled, skipping DNS operations.")
} }
return nil return nil
} }
var recordtype string var recordtype string
if addr.Is4() { if addr.Is4() {
recordtype = "A" recordtype = "A"
} else if addr.Is6() { } else if addr.Is6() {
recordtype = "AAAA" recordtype = "AAAA"
} else { } else {
return errors.New(addr.String() + " is not a valid IP address") return errors.New(addr.String() + " is not a valid IP address")
} }
fzone, fzoneerr := GetBestDNSZone(fqdn) fzone, fzoneerr := GetBestDNSZone(fqdn)
if fzoneerr != nil { if fzoneerr != nil {
fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS delete op\n", fqdn) fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS delete op\n", fqdn)
} else { } else {
_, frecordexists := fzone.GetRecord(fqdn, recordtype, addr.String()) _, frecordexists := fzone.GetRecord(fqdn, recordtype, addr.String())
if !frecordexists { if !frecordexists {
fmt.Printf("[DNS] DNS Record for %v doesn't exists, no need to change DNS.\n", fqdn) fmt.Printf("[DNS] DNS Record for %v doesn't exists, no need to change DNS.\n", fqdn)
} else { } else {
fpatcherr := fzone.SendPATCH(strings.Replace(fqdn+".", "."+fzone.Name, "", 1), addr.String(), recordtype, "DELETE") fpatcherr := fzone.SendPATCH(strings.Replace(fqdn+".", "."+fzone.Name, "", 1), addr.String(), recordtype, "DELETE")
if fpatcherr != nil { if fpatcherr != nil {
return fpatcherr return fpatcherr
} }
fmt.Printf("[DNS] - %v IN %v %v\n", fqdn, recordtype, addr.String()) fmt.Printf("[DNS] - %v IN %v %v\n", fqdn, recordtype, addr.String())
} }
} }
baseip := addr.StringExpanded() baseip := addr.StringExpanded()
var rfqdn string var rfqdn string
if addr.Is4() { if addr.Is4() {
a := strings.Split(baseip, ".") a := strings.Split(baseip, ".")
b := reverse(a) b := reverse(a)
rfqdn = strings.Join(b, ".") + ".in-addr.arpa" rfqdn = strings.Join(b, ".") + ".in-addr.arpa"
} else if addr.Is6() { } else if addr.Is6() {
a := strings.Replace(baseip, ":", "", -1) a := strings.Replace(baseip, ":", "", -1)
b := strings.Split(a, "") b := strings.Split(a, "")
c := reverse(b) c := reverse(b)
rfqdn = strings.Join(c, ".") + ".ip6.arpa" rfqdn = strings.Join(c, ".") + ".ip6.arpa"
} }
rzone, rzoneerr := GetBestDNSZone(rfqdn) rzone, rzoneerr := GetBestDNSZone(rfqdn)
if rzoneerr != nil { if rzoneerr != nil {
fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS delete op\n", rfqdn) fmt.Printf("[DNS] No suitable zone found for %v, skipping DNS delete op\n", rfqdn)
} else { } else {
_, rrecordexists := rzone.GetRecord(rfqdn, "PTR", fqdn) _, rrecordexists := rzone.GetRecord(rfqdn, "PTR", fqdn)
rhost := strings.Replace(rfqdn+".", "."+rzone.Name, "", 1) rhost := strings.Replace(rfqdn+".", "."+rzone.Name, "", 1)
if !rrecordexists { if !rrecordexists {
fmt.Printf("[DNS] Reverse DNS Record for %v doesn't exists, no need to change DNS.\n", addr.String()) fmt.Printf("[DNS] Reverse DNS Record for %v doesn't exists, no need to change DNS.\n", addr.String())
} else { } else {
rpatcherr := rzone.SendPATCH(rhost, fqdn, "PTR", "DELETE") rpatcherr := rzone.SendPATCH(rhost, fqdn, "PTR", "DELETE")
if rpatcherr != nil { if rpatcherr != nil {
return rpatcherr return rpatcherr
} }
fmt.Printf("[DNS] - %v IN %v %v\n", rfqdn, "PTR", fqdn) fmt.Printf("[DNS] - %v IN %v %v\n", rfqdn, "PTR", fqdn)
} }
} }
return nil return nil
} }

View file

@ -5,73 +5,73 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "ipam", Use: "ipam",
Short: "A cli based ipam", Short: "A cli based ipam",
Long: `A cli based ipam. Long: `A cli based ipam.
You can manage subnets, single ip addresses within those, and the corresponding A records. You can manage subnets, single ip addresses within those, and the corresponding A records.
PowerDNS and IPV6-Support will follow`, PowerDNS and IPV6-Support will follow`,
Version: ipam_version, Version: ipam_version,
} }
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
} }
} }
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
rootCmd.Flags().BoolP("debug", "d", false, "Enable debug mode. (may print sensitive Information, so please watch out!)") rootCmd.Flags().BoolP("debug", "d", false, "Enable debug mode. (may print sensitive Information, so please watch out!)")
} }
func initConfig() { func initConfig() {
// Find home directory. // Find home directory.
homedir, err := os.UserHomeDir() homedir, err := os.UserHomeDir()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
var ipamdir string = homedir + "/.ipam/" var ipamdir string = homedir + "/.ipam/"
// Search config in home directory with name ".cobra" (without extension). // Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(ipamdir) viper.AddConfigPath(ipamdir)
viper.SetConfigName("ipam") viper.SetConfigName("ipam")
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetDefault("DataPath", ipamdir+"data/") viper.SetDefault("DataPath", ipamdir+"data/")
viper.SetDefault("PowerDNSEnabled", false) viper.SetDefault("PowerDNSEnabled", false)
viper.SetDefault("PowerDNSEndpoint", "") viper.SetDefault("PowerDNSEndpoint", "")
viper.SetDefault("PowerDNSApiKey", "") viper.SetDefault("PowerDNSApiKey", "")
if err := viper.ReadInConfig(); err != nil { if err := viper.ReadInConfig(); err != nil {
_, patherr := os.Stat(ipamdir) _, patherr := os.Stat(ipamdir)
if patherr != nil { if patherr != nil {
mkerr := os.MkdirAll(ipamdir, 0755) mkerr := os.MkdirAll(ipamdir, 0755)
if mkerr != nil { if mkerr != nil {
println("[ERROR] Can't create ipam config directory!", mkerr) println("[ERROR] Can't create ipam config directory!", mkerr)
} }
} }
// I have no idea what's happening here... // I have no idea what's happening here...
if _, ok := err.(viper.ConfigFileNotFoundError); ok { if _, ok := err.(viper.ConfigFileNotFoundError); ok {
writeerr := viper.SafeWriteConfig() writeerr := viper.SafeWriteConfig()
if writeerr != nil { if writeerr != nil {
println("[ERROR] Can't write config file!", writeerr) println("[ERROR] Can't write config file!", writeerr)
} }
} else { } else {
println("[ERROR] Can't read config file!", err) println("[ERROR] Can't read config file!", err)
} }
} }
} }

View file

@ -5,14 +5,14 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"sort" "sort"
"strings" "strings"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
// FindBestSubnet tries to load the most fitting IP subnet file // FindBestSubnet tries to load the most fitting IP subnet file
@ -22,71 +22,71 @@ import (
// Returns the best subnet as Subnet object and true if a suitable // Returns the best subnet as Subnet object and true if a suitable
// subnet was found, otherwise an empty Subnet object and false. // subnet was found, otherwise an empty Subnet object and false.
func FindBestSubnet(ip netip.Addr) (Subnet, bool) { func FindBestSubnet(ip netip.Addr) (Subnet, bool) {
subnets := ListSubnets() subnets := ListSubnets()
var smallestprefix int = 0 var smallestprefix int = 0
bestmatch, _ := netip.ParsePrefix("0.0.0.0/32") bestmatch, _ := netip.ParsePrefix("0.0.0.0/32")
var isipv4 bool = ip.Is4() var isipv4 bool = ip.Is4()
var subnet Subnet var subnet Subnet
for _, net := range subnets { for _, net := range subnets {
prefix, _ := netip.ParsePrefix(net) prefix, _ := netip.ParsePrefix(net)
if prefix.Addr().Is4() == isipv4 { if prefix.Addr().Is4() == isipv4 {
if prefix.Contains(ip) { if prefix.Contains(ip) {
if prefix.Bits() > smallestprefix { if prefix.Bits() > smallestprefix {
bestmatch = prefix bestmatch = prefix
} }
} }
} }
} }
if !bestmatch.Addr().IsUnspecified() { if !bestmatch.Addr().IsUnspecified() {
var geterr error var geterr error
subnet, geterr = GetSubnet(bestmatch) subnet, geterr = GetSubnet(bestmatch)
if geterr != nil { if geterr != nil {
fmt.Println("[ERROR]", geterr) fmt.Println("[ERROR]", geterr)
os.Exit(1) os.Exit(1)
} }
return subnet, true return subnet, true
} else { } else {
return Subnet{}, false return Subnet{}, false
} }
} }
// SubnetExists will return true if the given subnet already exists // SubnetExists will return true if the given subnet already exists
// on file, false otherwise. // on file, false otherwise.
func SubnetExists(net netip.Prefix) bool { func SubnetExists(net netip.Prefix) bool {
subnets := ListSubnets() subnets := ListSubnets()
for _, b := range subnets { for _, b := range subnets {
if b == net.String() { if b == net.String() {
return true return true
} }
} }
return false return false
} }
// ListSubnets returns a list of strings containing the current // ListSubnets returns a list of strings containing the current
// subnets configured. // subnets configured.
func ListSubnets() []string { func ListSubnets() []string {
subnets := make([]string, 0) subnets := make([]string, 0)
var datadir string = viper.GetString("DataPath") var datadir string = viper.GetString("DataPath")
subnetfiles, readerr := os.ReadDir(datadir) subnetfiles, readerr := os.ReadDir(datadir)
if len(subnetfiles) == 0 { if len(subnetfiles) == 0 {
return subnets return subnets
} }
if readerr != nil { if readerr != nil {
fmt.Println("[ERROR]", readerr) fmt.Println("[ERROR]", readerr)
os.Exit(1) os.Exit(1)
} }
for _, element := range subnetfiles { for _, element := range subnetfiles {
a := strings.Replace(element.Name(), "_", "/", 1) a := strings.Replace(element.Name(), "_", "/", 1)
a = strings.Replace(a, ".json", "", 1) a = strings.Replace(a, ".json", "", 1)
subnets = append(subnets, a) subnets = append(subnets, a)
} }
return subnets return subnets
} }
// WriteSubnet takes a given Subnet object and tries to write it to // WriteSubnet takes a given Subnet object and tries to write it to
@ -94,35 +94,35 @@ func ListSubnets() []string {
// //
// Returns nil on success or the error that happened. // Returns nil on success or the error that happened.
func (s Subnet) WriteSubnet() error { func (s Subnet) WriteSubnet() error {
var datadir string = viper.GetString("DataPath") var datadir string = viper.GetString("DataPath")
_, direrr := os.Stat(datadir) _, direrr := os.Stat(datadir)
if direrr != nil { if direrr != nil {
mkerr := os.MkdirAll(datadir, 0755) mkerr := os.MkdirAll(datadir, 0755)
if mkerr != nil { if mkerr != nil {
println("[ERROR] Can't create ipam config directory!", mkerr) println("[ERROR] Can't create ipam config directory!", mkerr)
os.Exit(1) os.Exit(1)
} }
} }
filename := datadir + strings.Replace(s.Subnet.String(), "/", "_", 1) + ".json" filename := datadir + strings.Replace(s.Subnet.String(), "/", "_", 1) + ".json"
data, _ := json.Marshal(s) data, _ := json.Marshal(s)
file, fileerr := os.Create(filename) file, fileerr := os.Create(filename)
if fileerr != nil { if fileerr != nil {
fmt.Println("[ERROR]", fileerr) fmt.Println("[ERROR]", fileerr)
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer file.Close()
_, writeerr := file.Write(data) _, writeerr := file.Write(data)
if writeerr != nil { if writeerr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
return nil return nil
} }
// GetSubnet reads the corresponding file for the given // GetSubnet reads the corresponding file for the given
@ -131,35 +131,35 @@ func (s Subnet) WriteSubnet() error {
// Returns the Subnet object and nil if the file read was // Returns the Subnet object and nil if the file read was
// successful, an empty Subnet object and the error otherwise. // successful, an empty Subnet object and the error otherwise.
func GetSubnet(net netip.Prefix) (Subnet, error) { func GetSubnet(net netip.Prefix) (Subnet, error) {
var datadir string = viper.GetString("DataPath") var datadir string = viper.GetString("DataPath")
filename := datadir + strings.Replace(net.String(), "/", "_", 1) + ".json" filename := datadir + strings.Replace(net.String(), "/", "_", 1) + ".json"
var subnet Subnet = Subnet{} var subnet Subnet = Subnet{}
content, readerr := os.ReadFile(filename) content, readerr := os.ReadFile(filename)
if readerr != nil { if readerr != nil {
return Subnet{}, readerr return Subnet{}, readerr
} }
marsherr := json.Unmarshal(content, &subnet) marsherr := json.Unmarshal(content, &subnet)
if marsherr != nil { if marsherr != nil {
return Subnet{}, marsherr return Subnet{}, marsherr
} }
return subnet, nil return subnet, nil
} }
// SortAddresses sorts the given list of IP addresses // SortAddresses sorts the given list of IP addresses
// using netip.Addr.Less() and returns the sorted slice. // using netip.Addr.Less() and returns the sorted slice.
func SortAddresses(list []Address) []Address { func SortAddresses(list []Address) []Address {
if len(list) <= 1 { if len(list) <= 1 {
return list return list
} }
sort.Slice(list, func(i, j int) bool { sort.Slice(list, func(i, j int) bool {
return list[i].IP.Less(list[j].IP) return list[i].IP.Less(list[j].IP)
}) })
return list return list
} }
// DeleteSubnet deletes the subnet file on disk for netip.Prefix // DeleteSubnet deletes the subnet file on disk for netip.Prefix
@ -167,13 +167,13 @@ func SortAddresses(list []Address) []Address {
// //
// Returns nil on success, or a *PathError on failure // Returns nil on success, or a *PathError on failure
func DeleteSubnet(net netip.Prefix) error { func DeleteSubnet(net netip.Prefix) error {
var datadir string = viper.GetString("DataPath") var datadir string = viper.GetString("DataPath")
filename := datadir + strings.Replace(net.String(), "/", "_", 1) + ".json" filename := datadir + strings.Replace(net.String(), "/", "_", 1) + ".json"
removeerr := os.Remove(filename) removeerr := os.Remove(filename)
if removeerr != nil { if removeerr != nil {
return removeerr return removeerr
} else { } else {
return nil return nil
} }
} }

View file

@ -5,92 +5,92 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"os/user" "os/user"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// addCmd represents the add command // addCmd represents the add command
var subnetaddCmd = &cobra.Command{ var subnetaddCmd = &cobra.Command{
Use: "add subnet subnet-name [vlan]", Use: "add subnet subnet-name [vlan]",
Short: "Add a new subnet", Short: "Add a new subnet",
Long: `Add a new subnet`, Long: `Add a new subnet`,
Args: cobra.RangeArgs(2, 3), Args: cobra.RangeArgs(2, 3),
Aliases: []string{"a"}, Aliases: []string{"a"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var subnet string var subnet string
var vlanid string var vlanid string
var netname string var netname string
if len(args) == 2 { if len(args) == 2 {
subnet = args[0] subnet = args[0]
netname = args[1] netname = args[1]
vlanid = "-" vlanid = "-"
} }
if len(args) == 3 { if len(args) == 3 {
subnet = args[0] subnet = args[0]
netname = args[1] netname = args[1]
vlanid = args[2] vlanid = args[2]
} }
// Parse subnet into ParseCIDR to test if it's a valid subnet // Parse subnet into ParseCIDR to test if it's a valid subnet
// _, ipnet, err := net.ParseCIDR(subnet) // _, ipnet, err := net.ParseCIDR(subnet)
ipnet, parseerr := netip.ParsePrefix(subnet) ipnet, parseerr := netip.ParsePrefix(subnet)
// Exit if subnet already exists, no need to add it then // Exit if subnet already exists, no need to add it then
if SubnetExists(ipnet) { if SubnetExists(ipnet) {
fmt.Printf("[ERROR] Subnet already exists: %v\n", subnet) fmt.Printf("[ERROR] Subnet already exists: %v\n", subnet)
os.Exit(1) os.Exit(1)
} }
// Exit if parsed value is no valid IP // Exit if parsed value is no valid IP
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
// Exit if parsed value is an IPv6 Address // Exit if parsed value is an IPv6 Address
// TODO: Implement IPv6 support // TODO: Implement IPv6 support
if !ipnet.Addr().Is4() { // if !ipnet.Addr().Is4() {
fmt.Printf("[ERROR] IPv6 is not yet supported!\n") // fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
os.Exit(1) // os.Exit(1)
} // }
currentuser, _ := user.Current() currentuser, _ := user.Current()
subnetobject := Subnet{} subnetobject := Subnet{}
subnetobject.Subnet = ipnet subnetobject.Subnet = ipnet
subnetobject.Name = netname subnetobject.Name = netname
subnetobject.Vlan = vlanid subnetobject.Vlan = vlanid
subnetobject.ChangedAt = time.Now() subnetobject.ChangedAt = time.Now()
subnetobject.ChangedBy = currentuser.Username subnetobject.ChangedBy = currentuser.Username
writeerr := subnetobject.WriteSubnet() writeerr := subnetobject.WriteSubnet()
if writeerr != nil { if writeerr != nil {
fmt.Println("[ERROR]", writeerr) fmt.Println("[ERROR]", writeerr)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("added subnet:\nnet: %v\nname: %v\nvlan: %v\n", subnet, netname, vlanid) fmt.Printf("added subnet:\nnet: %v\nname: %v\nvlan: %v\n", subnet, netname, vlanid)
}, },
} }
func init() { func init() {
subnetCmd.AddCommand(subnetaddCmd) subnetCmd.AddCommand(subnetaddCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// addCmd.PersistentFlags().String("foo", "", "A help for foo") // addCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -4,82 +4,82 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// deleteCmd represents the delete command // deleteCmd represents the delete command
var subnetdeleteCmd = &cobra.Command{ var subnetdeleteCmd = &cobra.Command{
Use: "delete", Use: "delete",
Short: "delete subnet", Short: "delete subnet",
Long: `Delete a subnet from the ipam.`, Long: `Delete a subnet from the ipam.`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Aliases: []string{"d"}, Aliases: []string{"d"},
Example: "ipam subnet delete 192.168.0.0/24", Example: "ipam subnet delete 192.168.0.0/24",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
subnet, parseerr := netip.ParsePrefix(args[0]) subnet, parseerr := netip.ParsePrefix(args[0])
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
if !SubnetExists(subnet) { if !SubnetExists(subnet) {
fmt.Printf("[ERROR] Couldn't find subnet %v\n", subnet.String()) fmt.Printf("[ERROR] Couldn't find subnet %v\n", subnet.String())
os.Exit(1) os.Exit(1)
} }
subnetobj, suberr := GetSubnet(subnet) subnetobj, suberr := GetSubnet(subnet)
if suberr != nil { if suberr != nil {
fmt.Println("[ERROR]", suberr) fmt.Println("[ERROR]", suberr)
os.Exit(1) os.Exit(1)
} }
var confirmation string var confirmation string
skipinteractive, _ := cmd.Flags().GetBool("yes") skipinteractive, _ := cmd.Flags().GetBool("yes")
if skipinteractive { if skipinteractive {
confirmation = "y" confirmation = "y"
} else { } else {
fmt.Printf("[WARNING] Do you really want to delete subnet %v?\n", subnet.String()) fmt.Printf("[WARNING] Do you really want to delete subnet %v?\n", subnet.String())
fmt.Printf("[WARNING] This will also delete all DNS records if PowerDNS integration is enabled!\n") fmt.Printf("[WARNING] This will also delete all DNS records if PowerDNS integration is enabled!\n")
fmt.Printf("[WARNING] Continue? [y/N] ") fmt.Printf("[WARNING] Continue? [y/N] ")
fmt.Scan(&confirmation) fmt.Scan(&confirmation)
} }
if (confirmation == "y") || (confirmation == "Y") { if (confirmation == "y") || (confirmation == "Y") {
for _, address := range subnetobj.Addresses { for _, address := range subnetobj.Addresses {
if address.FQDN != "" { if address.FQDN != "" {
deleteerr := DeleteDNSFqdn(address.FQDN, address.IP) deleteerr := DeleteDNSFqdn(address.FQDN, address.IP)
if deleteerr != nil { if deleteerr != nil {
fmt.Println("[ERROR]", deleteerr) fmt.Println("[ERROR]", deleteerr)
} }
} }
} }
deleteerr := DeleteSubnet(subnet) deleteerr := DeleteSubnet(subnet)
if deleteerr != nil { if deleteerr != nil {
fmt.Println("[ERROR]", deleteerr) fmt.Println("[ERROR]", deleteerr)
os.Exit(1) os.Exit(1)
} else { } else {
fmt.Printf("deleted subnet %v\n", subnet.String()) fmt.Printf("deleted subnet %v\n", subnet.String())
} }
} }
}, },
} }
func init() { func init() {
subnetCmd.AddCommand(subnetdeleteCmd) subnetCmd.AddCommand(subnetdeleteCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// deleteCmd.PersistentFlags().String("foo", "", "A help for foo") // deleteCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
subnetdeleteCmd.Flags().BoolP("yes", "y", false, "suppress interactive prompts and answer yes.") subnetdeleteCmd.Flags().BoolP("yes", "y", false, "suppress interactive prompts and answer yes.")
} }

View file

@ -4,85 +4,85 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"math" "math"
"net/netip" "net/netip"
"os" "os"
"sort" "sort"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// listCmd represents the list command // listCmd represents the list command
var subnetlistCmd = &cobra.Command{ var subnetlistCmd = &cobra.Command{
Use: "list", Use: "list",
Short: "List all subnets", Short: "List all subnets",
Long: `List all subnets`, Long: `List all subnets`,
Aliases: []string{"l"}, Aliases: []string{"l"},
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
Example: "cmdb subnet list", Example: "cmdb subnet list",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
//verbose, _ := cmd.Flags().GetBool("verbose") //verbose, _ := cmd.Flags().GetBool("verbose")
subnetlist := ListSubnets() subnetlist := ListSubnets()
var subnets []Subnet var subnets []Subnet
for _, subnet := range subnetlist { for _, subnet := range subnetlist {
prefix, _ := netip.ParsePrefix(subnet) prefix, _ := netip.ParsePrefix(subnet)
net, err := GetSubnet(prefix) net, err := GetSubnet(prefix)
if err != nil { if err != nil {
fmt.Println("[ERROR]", err) fmt.Println("[ERROR]", err)
os.Exit(1) os.Exit(1)
} }
subnets = append(subnets, net) subnets = append(subnets, net)
} }
sort.Slice(subnets, func(i, j int) bool { sort.Slice(subnets, func(i, j int) bool {
return subnets[i].Subnet.Addr().Less(subnets[j].Subnet.Addr()) return subnets[i].Subnet.Addr().Less(subnets[j].Subnet.Addr())
}) })
fmt.Printf("%-15s VLAN %-25s Free IPs\n", "Prefix", "Name") fmt.Printf("%-18s VLAN %-25s Free IPs\n", "Prefix", "Name")
for _, subnet := range subnets { for _, subnet := range subnets {
var numip, freeip int var numip, freeip int
if subnet.Subnet.Addr().Is4() { if subnet.Subnet.Addr().Is4() {
hostbits := float64(32 - subnet.Subnet.Bits()) hostbits := float64(32 - subnet.Subnet.Bits())
if subnet.Subnet.Bits() == 31 { if subnet.Subnet.Bits() == 31 {
numip = 2 numip = 2
} else { } else {
numip = int(math.Pow(2, hostbits)) - 2 numip = int(math.Pow(2, hostbits)) - 2
} }
freeip = numip - len(subnet.Addresses) freeip = numip - len(subnet.Addresses)
if freeip > 1000 { if freeip > 1000 {
fmt.Printf("%-18s %-4s %-25s >1000\n", subnet.Subnet, subnet.Vlan, subnet.Name) fmt.Printf("%-18s %-4s %-25s >1000\n", subnet.Subnet, subnet.Vlan, subnet.Name)
} else { } else {
//fmt.Printf("| %-20s | %-20s |\n", "vegetables", "fruits") //fmt.Printf("| %-20s | %-20s |\n", "vegetables", "fruits")
fmt.Printf("%-18s %-4s %-25s %v\n", subnet.Subnet, subnet.Vlan, subnet.Name, freeip) fmt.Printf("%-18s %-4s %-25s %v\n", subnet.Subnet, subnet.Vlan, subnet.Name, freeip)
} }
} else { } else {
fmt.Printf("%v:\t%v\t(vl: %v)\n", subnet.Subnet, subnet.Name, subnet.Vlan) fmt.Printf("%v:\t%v\t(vl: %v)\n", subnet.Subnet, subnet.Name, subnet.Vlan)
//todo //todo
} }
//} else { //} else {
// fmt.Printf("%v:\t%v\t(vl: %v)\n", subnet.Subnet, subnet.Name, subnet.Vlan) // fmt.Printf("%v:\t%v\t(vl: %v)\n", subnet.Subnet, subnet.Name, subnet.Vlan)
//} //}
} }
}, },
} }
func init() { func init() {
subnetCmd.AddCommand(subnetlistCmd) subnetCmd.AddCommand(subnetlistCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// listCmd.PersistentFlags().String("foo", "", "A help for foo") // listCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// subnetlistCmd.Flags().BoolP("verbose", "v", false, "Show verbose output like free IPs") // subnetlistCmd.Flags().BoolP("verbose", "v", false, "Show verbose output like free IPs")
} }

View file

@ -4,69 +4,69 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// showCmd represents the show command // showCmd represents the show command
var subnetshowCmd = &cobra.Command{ var subnetshowCmd = &cobra.Command{
Use: "show subnet", Use: "show subnet",
Short: "Displays a subnet.", Short: "Displays a subnet.",
Long: `Displays a subnets details like name and vlan tag, Long: `Displays a subnets details like name and vlan tag,
aswell as a list of containing IP addresses`, aswell as a list of containing IP addresses`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Aliases: []string{"s"}, Aliases: []string{"s"},
Example: "ipam subnet show 192.168.0.0/24\nipam subnet show 2001:db8::/64", Example: "ipam subnet show 192.168.0.0/24\nipam subnet show 2001:db8::/64",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
net, parseerr := netip.ParsePrefix(args[0]) net, parseerr := netip.ParsePrefix(args[0])
if parseerr != nil { if parseerr != nil {
fmt.Println("[ERROR]", parseerr) fmt.Println("[ERROR]", parseerr)
os.Exit(1) os.Exit(1)
} }
if !SubnetExists(net) { if !SubnetExists(net) {
fmt.Printf("[ERROR] no subnet found for prefix: %v\n", args[0]) fmt.Printf("[ERROR] no subnet found for prefix: %v\n", args[0])
os.Exit(1) os.Exit(1)
} }
subnet, subneterr := GetSubnet(net) subnet, subneterr := GetSubnet(net)
if subneterr != nil { if subneterr != nil {
fmt.Println("[ERROR]", subneterr) fmt.Println("[ERROR]", subneterr)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("\n") fmt.Printf("\n")
fmt.Printf("Name: %v\n", subnet.Name) fmt.Printf("Name: %v\n", subnet.Name)
fmt.Printf("Vlan: %v\n", subnet.Vlan) fmt.Printf("Vlan: %v\n", subnet.Vlan)
fmt.Printf("Prefix: %v\n", subnet.Subnet) fmt.Printf("Prefix: %v\n", subnet.Subnet)
fmt.Printf("Modified at: %v\n", subnet.ChangedAt.Format(time.RFC1123)) fmt.Printf("Modified at: %v\n", subnet.ChangedAt.Format(time.RFC1123))
fmt.Printf("Modified by: %v\n\n", subnet.ChangedBy) fmt.Printf("Modified by: %v\n\n", subnet.ChangedBy)
fmt.Printf("%v:\n", subnet.Subnet) fmt.Printf("%v:\n", subnet.Subnet)
for _, element := range subnet.Addresses { for _, element := range subnet.Addresses {
if element.FQDN == "" { if element.FQDN == "" {
fmt.Printf("\t%v\n", element.IP.String()) fmt.Printf("\t%v\n", element.IP.String())
} else { } else {
fmt.Printf("\t%v: %v\n", element.IP.String(), element.FQDN) fmt.Printf("\t%v: %v\n", element.IP.String(), element.FQDN)
} }
} }
}, },
} }
func init() { func init() {
subnetCmd.AddCommand(subnetshowCmd) subnetCmd.AddCommand(subnetshowCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// showCmd.PersistentFlags().String("foo", "", "A help for foo") // showCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -5,27 +5,27 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd package cmd
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// subnetCmd represents the subnet command // subnetCmd represents the subnet command
var subnetCmd = &cobra.Command{ var subnetCmd = &cobra.Command{
Use: "subnet", Use: "subnet",
Short: "Manage ip subnets", Short: "Manage ip subnets",
Long: `Manage ip subnets`, Long: `Manage ip subnets`,
Aliases: []string{"s"}, Aliases: []string{"s"},
} }
func init() { func init() {
rootCmd.AddCommand(subnetCmd) rootCmd.AddCommand(subnetCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
// subnetCmd.PersistentFlags().String("foo", "", "A help for foo") // subnetCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// subnetCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // subnetCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View file

@ -7,5 +7,5 @@ package main
import "git.sr.ht/~lauralani/ipam/cmd" import "git.sr.ht/~lauralani/ipam/cmd"
func main() { func main() {
cmd.Execute() cmd.Execute()
} }