mirror of
https://codeberg.org/lauralani/ipam.git
synced 2024-11-24 04:30:02 +01:00
fix whitespace stuff
This commit is contained in:
parent
6d75376d7f
commit
67f9e0b6fb
19 changed files with 1059 additions and 1059 deletions
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ipam_version = "DEVEL"
|
ipam_version = "DEVEL"
|
||||||
)
|
)
|
||||||
|
|
142
cmd/export.go
142
cmd/export.go
|
@ -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.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
106
cmd/import.go
106
cmd/import.go
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
136
cmd/ip-add.go
136
cmd/ip-add.go
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
102
cmd/ip-delete.go
102
cmd/ip-delete.go
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
26
cmd/ip.go
26
cmd/ip.go
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
596
cmd/powerdns.go
596
cmd/powerdns.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
94
cmd/root.go
94
cmd/root.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
212
cmd/storage.go
212
cmd/storage.go
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
2
main.go
2
main.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue