Browse Source

fixing merge conflict

afeiszli 2 years ago
parent
commit
5d42face10

+ 1 - 0
.github/workflows/test.yml

@@ -49,6 +49,7 @@ jobs:
           go-version: 1.19
           go-version: 1.19
       - name: run tests
       - name: run tests
         run: |
         run: |
+          go vet ./...
           go test -p 1 ./... -v
           go test -p 1 ./... -v
         env:
         env:
           DATABASE: sqlite
           DATABASE: sqlite

+ 20 - 0
cli/cmd/host/add_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var addHostNetworkCmd = &cobra.Command{
+	Use:   "add_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Add a network to a host",
+	Long:  `Add a network to a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.AddHostToNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(addHostNetworkCmd)
+}

+ 20 - 0
cli/cmd/host/delete_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var deleteHostNetworkCmd = &cobra.Command{
+	Use:   "delete_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a network from a host",
+	Long:  `Delete a network from a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteHostFromNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(deleteHostNetworkCmd)
+}

+ 0 - 22
cli/cmd/host/update_networks.go

@@ -1,22 +0,0 @@
-package host
-
-import (
-	"strings"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var hostUpdateNetworksCmd = &cobra.Command{
-	Use:   "update_network HostID Networks(comma separated list)",
-	Args:  cobra.ExactArgs(2),
-	Short: "Update a host's networks",
-	Long:  `Update a host's networks`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.UpdateHostNetworks(args[0], strings.Split(args[1], ",")))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(hostUpdateNetworksCmd)
-}

+ 8 - 5
cli/functions/host.go

@@ -26,11 +26,14 @@ func UpdateHost(hostID string, body *models.ApiHost) *models.ApiHost {
 	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
 	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
 }
 }
 
 
-// UpdateHostNetworks - update a host's networks
-func UpdateHostNetworks(hostID string, networks []string) *hostNetworksUpdatePayload {
-	return request[hostNetworksUpdatePayload](http.MethodPut, "/api/hosts/"+hostID+"/networks", &hostNetworksUpdatePayload{
-		Networks: networks,
-	})
+// AddHostToNetwork - add a network to host
+func AddHostToNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodPost, "/api/hosts/"+hostID+"/networks/"+network, nil)
+}
+
+// DeleteHostFromNetwork - deletes a network from host
+func DeleteHostFromNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodDelete, "/api/hosts/"+hostID+"/networks/"+network, nil)
 }
 }
 
 
 // CreateRelay - turn a host into a relay
 // CreateRelay - turn a host into a relay

+ 18 - 3
controllers/dns.go

@@ -176,9 +176,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	logger.Log(1, "new DNS record added:", entry.Name)
 	logger.Log(1, "new DNS record added:", entry.Name)
 	if servercfg.IsMessageQueueBackend() {
 	if servercfg.IsMessageQueueBackend() {
-		if err = mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
-		}
+		go func() {
+			if err = mq.PublishPeerUpdate(); err != nil {
+				logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
+			}
+			if err := mq.PublishCustomDNS(&entry); err != nil {
+				logger.Log(0, "error publishing custom dns", err.Error())
+			}
+		}()
 	}
 	}
 	logger.Log(2, r.Header.Get("user"),
 	logger.Log(2, r.Header.Get("user"),
 		fmt.Sprintf("DNS entry is set: %+v", entry))
 		fmt.Sprintf("DNS entry is set: %+v", entry))
@@ -221,6 +226,16 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSDeleteByName,
+			Name:   entrytext,
+		}
+		if err := mq.PublishDNSUpdate(params["network"], dns); err != nil {
+			logger.Log(0, "failed to publish dns update", err.Error())
+		}
+	}()
+
 }
 }
 
 
 // GetDNSEntry - gets a DNS entry
 // GetDNSEntry - gets a DNS entry

+ 29 - 40
controllers/dns_test.go

@@ -6,17 +6,16 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
-	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
 )
 )
 
 
 var dnsHost models.Host
 var dnsHost models.Host
 
 
 func TestGetAllDNS(t *testing.T) {
 func TestGetAllDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -28,7 +27,7 @@ func TestGetAllDNS(t *testing.T) {
 	})
 	})
 	t.Run("OneEntry", func(t *testing.T) {
 	t.Run("OneEntry", func(t *testing.T) {
 		entry := models.DNSEntry{
 		entry := models.DNSEntry{
-			"10.0.0.3", "", "newhost", "skynet",
+			Address: "10.0.0.3", Name: "newhost", Network: "skynet",
 		}
 		}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -37,7 +36,7 @@ func TestGetAllDNS(t *testing.T) {
 		assert.Equal(t, 1, len(entries))
 		assert.Equal(t, 1, len(entries))
 	})
 	})
 	t.Run("MultipleEntry", func(t *testing.T) {
 	t.Run("MultipleEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.7", "", "anotherhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.7", Name: "anotherhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		entries, err := logic.GetAllDNS()
 		entries, err := logic.GetAllDNS()
@@ -47,7 +46,6 @@ func TestGetAllDNS(t *testing.T) {
 }
 }
 
 
 func TestGetNodeDNS(t *testing.T) {
 func TestGetNodeDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -94,7 +92,6 @@ func TestGetNodeDNS(t *testing.T) {
 	})
 	})
 }
 }
 func TestGetCustomDNS(t *testing.T) {
 func TestGetCustomDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -115,7 +112,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 0, len(dns))
 		assert.Equal(t, 0, len(dns))
 	})
 	})
 	t.Run("EntryExist", func(t *testing.T) {
 	t.Run("EntryExist", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "custom1", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "custom1", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
 		dns, err := logic.GetCustomDNS("skynet")
@@ -123,7 +120,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 		assert.Equal(t, 1, len(dns))
 	})
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
 	t.Run("MultipleEntries", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.4", "", "host4", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.4", Name: "host4", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
 		dns, err := logic.GetCustomDNS("skynet")
@@ -133,7 +130,6 @@ func TestGetCustomDNS(t *testing.T) {
 }
 }
 
 
 func TestGetDNSEntryNum(t *testing.T) {
 func TestGetDNSEntryNum(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -143,7 +139,7 @@ func TestGetDNSEntryNum(t *testing.T) {
 		assert.Equal(t, 0, num)
 		assert.Equal(t, 0, num)
 	})
 	})
 	t.Run("NodeExists", func(t *testing.T) {
 	t.Run("NodeExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
@@ -152,7 +148,6 @@ func TestGetDNSEntryNum(t *testing.T) {
 	})
 	})
 }
 }
 func TestGetDNS(t *testing.T) {
 func TestGetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -162,7 +157,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Nil(t, dns)
 		assert.Nil(t, dns)
 	})
 	})
 	t.Run("CustomDNSExists", func(t *testing.T) {
 	t.Run("CustomDNSExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
 		dns, err := logic.GetDNS("skynet")
@@ -182,7 +177,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 		assert.Equal(t, 1, len(dns))
 	})
 	})
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
 		dns, err := logic.GetDNS("skynet")
@@ -196,18 +191,16 @@ func TestGetDNS(t *testing.T) {
 }
 }
 
 
 func TestCreateDNS(t *testing.T) {
 func TestCreateDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	dns, err := logic.CreateDNS(entry)
 	dns, err := logic.CreateDNS(entry)
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.Equal(t, "newhost", dns.Name)
 	assert.Equal(t, "newhost", dns.Name)
 }
 }
 
 
 func TestSetDNS(t *testing.T) {
 func TestSetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -239,7 +232,7 @@ func TestSetDNS(t *testing.T) {
 		assert.Contains(t, string(content), "linuxhost.skynet")
 		assert.Contains(t, string(content), "linuxhost.skynet")
 	})
 	})
 	t.Run("EntryExists", func(t *testing.T) {
 	t.Run("EntryExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		err = logic.SetDNS()
 		err = logic.SetDNS()
@@ -255,12 +248,11 @@ func TestSetDNS(t *testing.T) {
 }
 }
 
 
 func TestGetDNSEntry(t *testing.T) {
 func TestGetDNSEntry(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	createTestNode()
 	createTestNode()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	_, _ = logic.CreateDNS(entry)
 	t.Run("wrong net", func(t *testing.T) {
 	t.Run("wrong net", func(t *testing.T) {
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
@@ -285,11 +277,10 @@ func TestGetDNSEntry(t *testing.T) {
 }
 }
 
 
 func TestDeleteDNS(t *testing.T) {
 func TestDeleteDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	_, _ = logic.CreateDNS(entry)
 	t.Run("EntryExists", func(t *testing.T) {
 	t.Run("EntryExists", func(t *testing.T) {
 		err := logic.DeleteDNS("newhost", "skynet")
 		err := logic.DeleteDNS("newhost", "skynet")
@@ -307,20 +298,19 @@ func TestDeleteDNS(t *testing.T) {
 }
 }
 
 
 func TestValidateDNSUpdate(t *testing.T) {
 func TestValidateDNSUpdate(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 	t.Run("BadNetwork", func(t *testing.T) {
 	t.Run("BadNetwork", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
 	})
 	t.Run("EmptyNetwork", func(t *testing.T) {
 	t.Run("EmptyNetwork", func(t *testing.T) {
-		//this can't actually happen as change.Network is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", ""}
+		// this can't actually happen as change.Network is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -333,14 +323,14 @@ func TestValidateDNSUpdate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
 	t.Run("BadAddress", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		change := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	})
 	t.Run("EmptyName", func(t *testing.T) {
 	t.Run("EmptyName", func(t *testing.T) {
-		//this can't actually happen as change.Name is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		// this can't actually happen as change.Name is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -350,29 +340,28 @@ func TestValidateDNSUpdate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 			name = name + "a"
 		}
 		}
-		change := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	})
 	t.Run("NameUnique", func(t *testing.T) {
 	t.Run("NameUnique", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "wirecat"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "wirecat"}
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(change)
 		_, _ = logic.CreateDNS(change)
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
-		//cleanup
+		// cleanup
 		err = logic.DeleteDNS("myhost", "wirecat")
 		err = logic.DeleteDNS("myhost", "wirecat")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 
 
 }
 }
 func TestValidateDNSCreate(t *testing.T) {
 func TestValidateDNSCreate(t *testing.T) {
-	database.InitializeDatabase()
 	_ = logic.DeleteDNS("mynode", "skynet")
 	_ = logic.DeleteDNS("mynode", "skynet")
 	t.Run("NoNetwork", func(t *testing.T) {
 	t.Run("NoNetwork", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -384,13 +373,13 @@ func TestValidateDNSCreate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
 	t.Run("BadAddress", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	})
 	t.Run("EmptyName", func(t *testing.T) {
 	t.Run("EmptyName", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -400,13 +389,13 @@ func TestValidateDNSCreate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 			name = name + "a"
 		}
 		}
-		entry := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	})
 	t.Run("NameUnique", func(t *testing.T) {
 	t.Run("NameUnique", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(entry)
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)

+ 27 - 10
controllers/ext_client.go

@@ -388,10 +388,15 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 
 
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
-	err = mq.PublishExtPeerUpdate(&node)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
-	}
+	go func() {
+		err = mq.PublishExtPeerUpdate(&node)
+		if err != nil {
+			logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
+		}
+		if err := mq.PublishExtCLientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing extclient dns", err.Error())
+		}
+	}()
 }
 }
 
 
 // swagger:route PUT /api/extclients/{network}/{clientid} ext_client updateExtClient
 // swagger:route PUT /api/extclients/{network}/{clientid} ext_client updateExtClient
@@ -459,7 +464,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-
 	if changedID && oldExtClient.OwnerID != "" {
 	if changedID && oldExtClient.OwnerID != "" {
 		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
 		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
@@ -471,7 +475,8 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	// == END PRO ==
 	// == END PRO ==
 
 
 	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
 	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
-
+	// extra var need as logic.Update changes oldExtClient
+	currentClient := oldExtClient
 	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
 	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
@@ -490,6 +495,13 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(newclient)
 	json.NewEncoder(w).Encode(newclient)
+	if changedID {
+		go func() {
+			if err := mq.PublishExtClientDNSUpdate(currentClient, newExtClient, networkName); err != nil {
+				logger.Log(1, "error pubishing dns update for extcient update", err.Error())
+			}
+		}()
+	}
 }
 }
 
 
 // swagger:route DELETE /api/extclients/{network}/{clientid} ext_client deleteExtClient
 // swagger:route DELETE /api/extclients/{network}/{clientid} ext_client deleteExtClient
@@ -554,10 +566,15 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	err = mq.PublishExtPeerUpdate(&ingressnode)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
-	}
+	go func() {
+		err = mq.PublishExtPeerUpdate(&ingressnode)
+		if err != nil {
+			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
+		}
+		if err := mq.PublishDeleteExtClientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing dns update for extclient deletion", err.Error())
+		}
+	}()
 
 
 	logger.Log(0, r.Header.Get("user"),
 	logger.Log(0, r.Header.Get("user"),
 		"Deleted extclient client", params["clientid"], "from network", params["network"])
 		"Deleted extclient client", params["clientid"], "from network", params["network"])

+ 19 - 0
controllers/hosts.go

@@ -108,6 +108,19 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 		}
 		}
+		if newHost.Name != currHost.Name {
+			networks := logic.GetHostNetworks(currHost.ID.String())
+			if err := mq.PublishHostDNSUpdate(currHost, newHost, networks); err != nil {
+				var dnsError *models.DNSError
+				if errors.Is(err, dnsError) {
+					for _, message := range err.(models.DNSError).ErrorStrings {
+						logger.Log(0, message)
+					}
+				} else {
+					logger.Log(0, err.Error())
+				}
+			}
+		}
 	}()
 	}()
 
 
 	apiHostData := newHost.ConvertNMHostToAPI()
 	apiHostData := newHost.ConvertNMHostToAPI()
@@ -268,17 +281,23 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+	node.Action = models.NODE_DELETE
+	node.PendingDelete = true
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
 	if err := logic.DeleteNode(node, false); err != nil {
 	if err := logic.DeleteNode(node, false); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 		return
 	}
 	}
 	// notify node change
 	// notify node change
+
 	runUpdates(node, false)
 	runUpdates(node, false)
 	go func() { // notify of peer change
 	go func() { // notify of peer change
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
 		}
+		if err := mq.PublishDNSDelete(node, currHost); err != nil {
+			logger.Log(1, "error publishing dns update", err.Error())
+		}
 	}()
 	}()
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("removed host %s from network %s", currHost.Name, network))
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("removed host %s from network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)

+ 22 - 24
controllers/network_test.go

@@ -1,11 +1,13 @@
 package controller
 package controller
 
 
 import (
 import (
+	"context"
 	"os"
 	"os"
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
@@ -20,8 +22,27 @@ type NetworkValidationTestCase struct {
 
 
 var netHost models.Host
 var netHost models.Host
 
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestCreateNetwork(t *testing.T) {
 func TestCreateNetwork(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 	deleteAllNetworks()
 
 
 	var network models.Network
 	var network models.Network
@@ -34,7 +55,6 @@ func TestCreateNetwork(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 }
 }
 func TestGetNetwork(t *testing.T) {
 func TestGetNetwork(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 
 
 	t.Run("GetExistingNetwork", func(t *testing.T) {
 	t.Run("GetExistingNetwork", func(t *testing.T) {
@@ -50,7 +70,6 @@ func TestGetNetwork(t *testing.T) {
 }
 }
 
 
 func TestDeleteNetwork(t *testing.T) {
 func TestDeleteNetwork(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	//create nodes
 	//create nodes
 	t.Run("NetworkwithNodes", func(t *testing.T) {
 	t.Run("NetworkwithNodes", func(t *testing.T) {
@@ -66,7 +85,6 @@ func TestDeleteNetwork(t *testing.T) {
 }
 }
 
 
 func TestCreateKey(t *testing.T) {
 func TestCreateKey(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	keys, _ := logic.GetKeys("skynet")
 	keys, _ := logic.GetKeys("skynet")
 	for _, key := range keys {
 	for _, key := range keys {
@@ -138,7 +156,6 @@ func TestCreateKey(t *testing.T) {
 }
 }
 
 
 func TestGetKeys(t *testing.T) {
 func TestGetKeys(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
 	network, err := logic.GetNetwork("skynet")
@@ -161,7 +178,6 @@ func TestGetKeys(t *testing.T) {
 	})
 	})
 }
 }
 func TestDeleteKey(t *testing.T) {
 func TestDeleteKey(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
 	network, err := logic.GetNetwork("skynet")
 	assert.Nil(t, err)
 	assert.Nil(t, err)
@@ -183,7 +199,6 @@ func TestDeleteKey(t *testing.T) {
 func TestSecurityCheck(t *testing.T) {
 func TestSecurityCheck(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 	//these seem to work but not sure it the tests are really testing the functionality
 
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	os.Setenv("MASTER_KEY", "secretkey")
 	t.Run("NoNetwork", func(t *testing.T) {
 	t.Run("NoNetwork", func(t *testing.T) {
 		networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
 		networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
@@ -214,7 +229,6 @@ func TestValidateNetwork(t *testing.T) {
 	//t.Skip()
 	//t.Skip()
 	//This functions is not called by anyone
 	//This functions is not called by anyone
 	//it panics as validation function 'display_name_valid' is not defined
 	//it panics as validation function 'display_name_valid' is not defined
-	initialize()
 	//yes := true
 	//yes := true
 	//no := false
 	//no := false
 	//deleteNet(t)
 	//deleteNet(t)
@@ -291,7 +305,6 @@ func TestValidateNetwork(t *testing.T) {
 func TestIpv6Network(t *testing.T) {
 func TestIpv6Network(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 	//these seem to work but not sure it the tests are really testing the functionality
 
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	os.Setenv("MASTER_KEY", "secretkey")
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -318,21 +331,6 @@ func deleteAllNetworks() {
 	}
 	}
 }
 }
 
 
-func initialize() {
-	database.InitializeDatabase()
-	createAdminUser()
-}
-
-func createAdminUser() {
-	logic.CreateAdmin(&models.User{
-		UserName: "admin",
-		Password: "password",
-		IsAdmin:  true,
-		Networks: []string{},
-		Groups:   []string{},
-	})
-}
-
 func createNet() {
 func createNet() {
 	var network models.Network
 	var network models.Network
 	network.NetID = "skynet"
 	network.NetID = "skynet"

+ 36 - 0
controllers/node.go

@@ -657,6 +657,30 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}()
 	}()
 	//runForceServerUpdate(&data.Node, true)
 	//runForceServerUpdate(&data.Node, true)
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSInsert,
+			Name:   data.Host.Name + "." + data.Node.Network,
+		}
+		if data.Node.Address.IP != nil {
+			dns.Address = data.Node.Address.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		if data.Node.Address6.IP != nil {
+			dns.Address = data.Node.Address6.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		//publish add dns records for network to new node
+		if err := mq.PublishAllDNS(&data.Node); err != nil {
+			logger.Log(1, "failed to publish dns update on node creation", err.Error())
+		}
+	}()
 }
 }
 
 
 // == EGRESS ==
 // == EGRESS ==
@@ -929,6 +953,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNode)
 	json.NewEncoder(w).Encode(apiNode)
 
 
 	runUpdates(newNode, ifaceDelta)
 	runUpdates(newNode, ifaceDelta)
+	go func() {
+		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
+			logger.Log(1, "failed to publish dns update", err.Error())
+		}
+	}()
 }
 }
 
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
@@ -976,6 +1005,13 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
 		}
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(1, "failed to retrieve host for node", node.ID.String(), err.Error())
+		}
+		if err := mq.PublishDNSDelete(&node, host); err != nil {
+			logger.Log(1, "error publishing dns update", err.Error())
+		}
 	}()
 	}()
 }
 }
 
 

+ 0 - 3
controllers/node_test.go

@@ -21,7 +21,6 @@ func TestCreateEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
 	gateway.Ranges = []string{"10.100.100.0/24"}
 	gateway.Ranges = []string{"10.100.100.0/24"}
 	gateway.NetID = "skynet"
 	gateway.NetID = "skynet"
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	t.Run("NoNodes", func(t *testing.T) {
 	t.Run("NoNodes", func(t *testing.T) {
@@ -78,7 +77,6 @@ func TestCreateEgressGateway(t *testing.T) {
 }
 }
 func TestDeleteEgressGateway(t *testing.T) {
 func TestDeleteEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	testnode := createTestNode()
 	testnode := createTestNode()
@@ -110,7 +108,6 @@ func TestDeleteEgressGateway(t *testing.T) {
 }
 }
 
 
 func TestGetNetworkNodes(t *testing.T) {
 func TestGetNetworkNodes(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	t.Run("BadNet", func(t *testing.T) {
 	t.Run("BadNet", func(t *testing.T) {

+ 62 - 49
controllers/user_test.go

@@ -3,22 +3,24 @@ package controller
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/gravitl/netmaker/database"
+	"github.com/stretchr/testify/assert"
+
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/stretchr/testify/assert"
 )
 )
 
 
-func deleteAllUsers() {
+func deleteAllUsers(t *testing.T) {
+	t.Helper()
 	users, _ := logic.GetUsers()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 	for _, user := range users {
-		logic.DeleteUser(user.UserName)
+		if _, err := logic.DeleteUser(user.UserName); err != nil {
+			t.Fatal(err)
+		}
 	}
 	}
 }
 }
 
 
 func TestHasAdmin(t *testing.T) {
 func TestHasAdmin(t *testing.T) {
-	//delete all current users
-	database.InitializeDatabase()
+	// delete all current users
 	users, _ := logic.GetUsers()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 	for _, user := range users {
 		success, err := logic.DeleteUser(user.UserName)
 		success, err := logic.DeleteUser(user.UserName)
@@ -31,7 +33,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 		assert.False(t, found)
 	})
 	})
 	t.Run("No admin user", func(t *testing.T) {
 	t.Run("No admin user", func(t *testing.T) {
-		var user = models.User{"noadmin", "password", nil, false, nil}
+		var user = models.User{UserName: "noadmin", Password: "password"}
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
@@ -39,7 +41,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 		assert.False(t, found)
 	})
 	})
 	t.Run("admin user", func(t *testing.T) {
 	t.Run("admin user", func(t *testing.T) {
-		var user = models.User{"admin", "password", nil, true, nil}
+		var user = models.User{UserName: "admin", Password: "password", IsAdmin: true}
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
@@ -47,8 +49,8 @@ func TestHasAdmin(t *testing.T) {
 		assert.True(t, found)
 		assert.True(t, found)
 	})
 	})
 	t.Run("multiple admins", func(t *testing.T) {
 	t.Run("multiple admins", func(t *testing.T) {
-		var user = models.User{"admin1", "password", nil, true, nil}
-		 err := logic.CreateUser(&user)
+		var user = models.User{UserName: "admin1", Password: "password", IsAdmin: true}
+		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -57,9 +59,8 @@ func TestHasAdmin(t *testing.T) {
 }
 }
 
 
 func TestCreateUser(t *testing.T) {
 func TestCreateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
 	t.Run("NoUser", func(t *testing.T) {
 	t.Run("NoUser", func(t *testing.T) {
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -72,8 +73,7 @@ func TestCreateUser(t *testing.T) {
 }
 }
 
 
 func TestCreateAdmin(t *testing.T) {
 func TestCreateAdmin(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	var user models.User
 	var user models.User
 	t.Run("NoAdmin", func(t *testing.T) {
 	t.Run("NoAdmin", func(t *testing.T) {
 		user.UserName = "admin"
 		user.UserName = "admin"
@@ -90,16 +90,17 @@ func TestCreateAdmin(t *testing.T) {
 }
 }
 
 
 func TestDeleteUser(t *testing.T) {
 func TestDeleteUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	t.Run("NonExistent User", func(t *testing.T) {
 	t.Run("NonExistent User", func(t *testing.T) {
 		deleted, err := logic.DeleteUser("admin")
 		deleted, err := logic.DeleteUser("admin")
 		assert.EqualError(t, err, "user does not exist")
 		assert.EqualError(t, err, "user does not exist")
 		assert.False(t, deleted)
 		assert.False(t, deleted)
 	})
 	})
 	t.Run("Existing User", func(t *testing.T) {
 	t.Run("Existing User", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Fatal(err)
+		}
 		deleted, err := logic.DeleteUser("admin")
 		deleted, err := logic.DeleteUser("admin")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, deleted)
 		assert.True(t, deleted)
@@ -107,7 +108,6 @@ func TestDeleteUser(t *testing.T) {
 }
 }
 
 
 func TestValidateUser(t *testing.T) {
 func TestValidateUser(t *testing.T) {
-	database.InitializeDatabase()
 	var user models.User
 	var user models.User
 	t.Run("Valid Create", func(t *testing.T) {
 	t.Run("Valid Create", func(t *testing.T) {
 		user.UserName = "admin"
 		user.UserName = "admin"
@@ -126,21 +126,21 @@ func TestValidateUser(t *testing.T) {
 		user.UserName = "*invalid"
 		user.UserName = "*invalid"
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.Error(t, err)
 		assert.Error(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("Short UserName", func(t *testing.T) {
 	t.Run("Short UserName", func(t *testing.T) {
 		t.Skip()
 		t.Skip()
 		user.UserName = "1"
 		user.UserName = "1"
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("Empty UserName", func(t *testing.T) {
 	t.Run("Empty UserName", func(t *testing.T) {
 		t.Skip()
 		t.Skip()
 		user.UserName = ""
 		user.UserName = ""
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.EqualError(t, err, "some string")
 		assert.EqualError(t, err, "some string")
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("EmptyPassword", func(t *testing.T) {
 	t.Run("EmptyPassword", func(t *testing.T) {
 		user.Password = ""
 		user.Password = ""
@@ -155,16 +155,19 @@ func TestValidateUser(t *testing.T) {
 }
 }
 
 
 func TestGetUser(t *testing.T) {
 func TestGetUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUser("admin")
 		admin, err := logic.GetUser("admin")
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, "", admin.UserName)
 		assert.Equal(t, "", admin.UserName)
 	})
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.GetUser("admin")
 		admin, err := logic.GetUser("admin")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
 		assert.Equal(t, user.UserName, admin.UserName)
@@ -172,30 +175,36 @@ func TestGetUser(t *testing.T) {
 }
 }
 
 
 func TestGetUsers(t *testing.T) {
 func TestGetUsers(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	adminUser := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: false}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUsers()
 		admin, err := logic.GetUsers()
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&adminUser); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Equal(t, user.UserName, admins[0].UserName)
+		assert.Equal(t, adminUser.UserName, admins[0].UserName)
 	})
 	})
 	t.Run("MulipleUsers", func(t *testing.T) {
 	t.Run("MulipleUsers", func(t *testing.T) {
-		user := models.User{"user", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		for _, u := range admins {
 		for _, u := range admins {
 			if u.UserName == "admin" {
 			if u.UserName == "admin" {
-				assert.Equal(t, "admin", u.UserName)
+				assert.Equal(t, true, u.IsAdmin)
 			} else {
 			} else {
 				assert.Equal(t, user.UserName, u.UserName)
 				assert.Equal(t, user.UserName, u.UserName)
+				assert.Equal(t, user.IsAdmin, u.IsAdmin)
 			}
 			}
 		}
 		}
 	})
 	})
@@ -203,10 +212,9 @@ func TestGetUsers(t *testing.T) {
 }
 }
 
 
 func TestUpdateUser(t *testing.T) {
 func TestUpdateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
-	newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true, []string{}}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	newuser := models.User{UserName: "hello", Password: "world", Networks: []string{"wirecat, netmaker"}, IsAdmin: true, Groups: []string{}}
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.UpdateUser(&newuser, &user)
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
@@ -214,7 +222,9 @@ func TestUpdateUser(t *testing.T) {
 	})
 	})
 
 
 	t.Run("UserExists", func(t *testing.T) {
 	t.Run("UserExists", func(t *testing.T) {
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.UpdateUser(&newuser, &user)
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.Equal(t, newuser.UserName, admin.UserName)
 		assert.Equal(t, newuser.UserName, admin.UserName)
@@ -246,8 +256,8 @@ func TestUpdateUser(t *testing.T) {
 // }
 // }
 
 
 func TestVerifyAuthRequest(t *testing.T) {
 func TestVerifyAuthRequest(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
 	var authRequest models.UserAuthParams
 	var authRequest models.UserAuthParams
 	t.Run("EmptyUserName", func(t *testing.T) {
 	t.Run("EmptyUserName", func(t *testing.T) {
 		authRequest.UserName = ""
 		authRequest.UserName = ""
@@ -271,23 +281,26 @@ func TestVerifyAuthRequest(t *testing.T) {
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 	})
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
 	t.Run("Non-Admin", func(t *testing.T) {
-		user := models.User{"nonadmin", "somepass", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"nonadmin", "somepass"}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "nonadmin", Password: "somepass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.NotNil(t, jwt)
 		assert.NotNil(t, jwt)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 	t.Run("WrongPassword", func(t *testing.T) {
 	t.Run("WrongPassword", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"admin", "badpass"}
+		user := models.User{UserName: "admin", Password: "password", Groups: []string{}}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "badpass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "incorrect credentials")
 		assert.EqualError(t, err, "incorrect credentials")
 	})
 	})
 	t.Run("Success", func(t *testing.T) {
 	t.Run("Success", func(t *testing.T) {
-		authRequest := models.UserAuthParams{"admin", "password"}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "password"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.NotNil(t, jwt)
 		assert.NotNil(t, jwt)

+ 22 - 8
functions/helpers_test.go

@@ -1,10 +1,12 @@
 package functions
 package functions
 
 
 import (
 import (
+	"context"
 	"encoding/json"
 	"encoding/json"
 	"testing"
 	"testing"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
@@ -19,11 +21,27 @@ var (
 	}
 	}
 )
 )
 
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestNetworkExists(t *testing.T) {
 func TestNetworkExists(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	defer database.CloseDB()
 	defer database.CloseDB()
 	exists, err := logic.NetworkExists(testNetwork.NetID)
 	exists, err := logic.NetworkExists(testNetwork.NetID)
@@ -53,10 +71,6 @@ func TestNetworkExists(t *testing.T) {
 }
 }
 
 
 func TestGetAllExtClients(t *testing.T) {
 func TestGetAllExtClients(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	defer database.CloseDB()
 	defer database.CloseDB()
 	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
 	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
 
 

+ 8 - 9
go.mod

@@ -4,7 +4,7 @@ go 1.19
 
 
 require (
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.11.1
+	github.com/go-playground/validator/v10 v10.11.2
 	github.com/golang-jwt/jwt/v4 v4.4.3
 	github.com/golang-jwt/jwt/v4 v4.4.3
 	github.com/google/uuid v1.3.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
@@ -15,11 +15,11 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.1
 	github.com/stretchr/testify v1.8.1
 	github.com/txn2/txeh v1.3.0
 	github.com/txn2/txeh v1.3.0
-	golang.org/x/crypto v0.3.0
-	golang.org/x/net v0.4.0 // indirect
-	golang.org/x/oauth2 v0.3.0
-	golang.org/x/sys v0.3.0 // indirect
-	golang.org/x/text v0.5.0 // indirect
+	golang.org/x/crypto v0.6.0
+	golang.org/x/net v0.6.0 // indirect
+	golang.org/x/oauth2 v0.4.0
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -29,7 +29,6 @@ require (
 require (
 require (
 	filippo.io/edwards25519 v1.0.0
 	filippo.io/edwards25519 v1.0.0
 	github.com/c-robinson/iplib v1.0.6
 	github.com/c-robinson/iplib v1.0.6
-	github.com/go-ping/ping v1.1.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 )
 
 
@@ -60,8 +59,8 @@ require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute v1.12.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
-	github.com/go-playground/locales v0.14.0 // indirect
-	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/hashicorp/go-version v1.6.0

+ 18 - 32
go.sum

@@ -17,7 +17,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -29,16 +28,13 @@ github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
-github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
-github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
 github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
 github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -52,7 +48,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -73,13 +68,10 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
@@ -106,7 +98,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -116,9 +107,7 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -160,10 +149,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -172,17 +160,18 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
+golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
+golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -193,10 +182,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -205,8 +192,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -215,8 +203,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -240,11 +229,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=

+ 22 - 1
logic/host_test.go

@@ -1,17 +1,38 @@
 package logic
 package logic
 
 
 import (
 import (
+	"context"
 	"net"
 	"net"
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/matryer/is"
 	"github.com/matryer/is"
 )
 )
 
 
-func TestCheckPorts(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
+	defer database.CloseDB()
+	CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
+func TestCheckPorts(t *testing.T) {
 	h := models.Host{
 	h := models.Host{
 		ID:              uuid.New(),
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),
 		EndpointIP:      net.ParseIP("192.168.1.1"),

+ 1 - 0
logic/hosts.go

@@ -96,6 +96,7 @@ func CreateHost(h *models.Host) error {
 		return err
 		return err
 	}
 	}
 	h.HostPass = string(hash)
 	h.HostPass = string(hash)
+	checkForZombieHosts(h)
 	return UpsertHost(h)
 	return UpsertHost(h)
 }
 }
 
 

+ 1 - 1
logic/nodes.go

@@ -534,7 +534,7 @@ func createNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	CheckZombies(node, host.MacAddress)
+	CheckZombies(node)
 
 
 	nodebytes, err := json.Marshal(&node)
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {

+ 19 - 31
logic/peers.go

@@ -301,7 +301,6 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 	hostPeerUpdate := models.HostPeerUpdate{
 	hostPeerUpdate := models.HostPeerUpdate{
 		Host:          *host,
 		Host:          *host,
 		Server:        servercfg.GetServer(),
 		Server:        servercfg.GetServer(),
-		Network:       make(map[string]models.NetworkInfo),
 		PeerIDs:       make(models.HostPeerMap),
 		PeerIDs:       make(models.HostPeerMap),
 		ServerVersion: servercfg.GetVersion(),
 		ServerVersion: servercfg.GetVersion(),
 		ServerAddrs:   []models.ServerAddr{},
 		ServerAddrs:   []models.ServerAddr{},
@@ -320,10 +319,6 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 		if !node.Connected || node.Action == models.NODE_DELETE || node.PendingDelete {
 		if !node.Connected || node.Action == models.NODE_DELETE || node.PendingDelete {
 			continue
 			continue
 		}
 		}
-
-		hostPeerUpdate.Network[node.Network] = models.NetworkInfo{
-			DNS: getPeerDNS(node.Network),
-		}
 		currentPeers, err := GetNetworkNodes(node.Network)
 		currentPeers, err := GetNetworkNodes(node.Network)
 		if err != nil {
 		if err != nil {
 			log.Println("no network nodes")
 			log.Println("no network nodes")
@@ -395,7 +390,25 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			}
 			peerConfig.AllowedIPs = allowedips
 			peerConfig.AllowedIPs = allowedips
 			if node.IsIngressGateway || node.IsEgressGateway {
 			if node.IsIngressGateway || node.IsEgressGateway {
-
+				if peer.IsIngressGateway {
+					_, extPeerIDAndAddrs, err := getExtPeers(&peer)
+					if err == nil {
+						for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+							nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+								PeerAddr: net.IPNet{
+									IP:   net.ParseIP(extPeerIdAndAddr.Address),
+									Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+								},
+								PeerKey: extPeerIdAndAddr.ID,
+								Allow:   true,
+							}
+						}
+					}
+				}
+				if node.IsIngressGateway && peer.IsEgressGateway {
+					hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
+						peer.EgressGatewayRanges...)
+				}
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 					PeerAddr: net.IPNet{
 					PeerAddr: net.IPNet{
 						IP:   net.ParseIP(peer.PrimaryAddress()),
 						IP:   net.ParseIP(peer.PrimaryAddress()),
@@ -508,7 +521,6 @@ func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, err
 	peerUpdate := models.PeerUpdate{
 	peerUpdate := models.PeerUpdate{
 		Network:       node.Network,
 		Network:       node.Network,
 		ServerVersion: ncutils.Version,
 		ServerVersion: ncutils.Version,
-		DNS:           getPeerDNS(node.Network),
 		PeerIDs:       make(models.PeerMap),
 		PeerIDs:       make(models.PeerMap),
 	}
 	}
 	currentPeers, err := GetNetworkNodes(node.Network)
 	currentPeers, err := GetNetworkNodes(node.Network)
@@ -777,7 +789,6 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 	})
 	})
 	peerUpdate.Peers = peers
 	peerUpdate.Peers = peers
 	peerUpdate.ServerAddrs = serverNodeAddresses
 	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
 	peerUpdate.PeerIDs = peerMap
 	peerUpdate.PeerIDs = peerMap
 	return peerUpdate, nil
 	return peerUpdate, nil
 }
 }
@@ -948,28 +959,6 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
 	return allowedips
 	return allowedips
 }
 }
 
 
-func getPeerDNS(network string) string {
-	var dns string
-	if nodes, err := GetNetworkNodes(network); err == nil {
-		for i, node := range nodes {
-			host, err := GetHost(node.HostID.String())
-			if err != nil {
-				logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
-				continue
-			}
-			dns = dns + fmt.Sprintf("%s %s.%s\n", nodes[i].Address, host.Name, nodes[i].Network)
-		}
-	}
-
-	if customDNSEntries, err := GetCustomDNS(network); err == nil {
-		for _, entry := range customDNSEntries {
-			// TODO - filter entries based on ACLs / given peers vs nodes in network
-			dns = dns + fmt.Sprintf("%s %s.%s\n", entry.Address, entry.Name, entry.Network)
-		}
-	}
-	return dns
-}
-
 // GetPeerUpdateForRelayedNode - calculates peer update for a relayed node by getting the relay
 // GetPeerUpdateForRelayedNode - calculates peer update for a relayed node by getting the relay
 // copying the relay node's allowed ips and making appropriate substitutions
 // copying the relay node's allowed ips and making appropriate substitutions
 func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string) (models.PeerUpdate, error) {
 func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string) (models.PeerUpdate, error) {
@@ -1108,7 +1097,6 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 	})
 	})
 	peerUpdate.Peers = peers
 	peerUpdate.Peers = peers
 	peerUpdate.ServerAddrs = serverNodeAddresses
 	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
 	return peerUpdate, nil
 	return peerUpdate, nil
 }
 }
 
 

+ 5 - 1
logic/pro/networkuser_test.go

@@ -10,8 +10,12 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
 
 
-func TestNetworkUserLogic(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
+	defer database.CloseDB()
+}
+
+func TestNetworkUserLogic(t *testing.T) {
 	networkUser := promodels.NetworkUser{
 	networkUser := promodels.NetworkUser{
 		ID: "helloworld",
 		ID: "helloworld",
 	}
 	}

+ 0 - 2
logic/pro/usergroups_test.go

@@ -3,13 +3,11 @@ package pro
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
 
 
 func TestUserGroupLogic(t *testing.T) {
 func TestUserGroupLogic(t *testing.T) {
-	database.InitializeDatabase()
 
 
 	t.Run("User Groups initialized successfully", func(t *testing.T) {
 	t.Run("User Groups initialized successfully", func(t *testing.T) {
 		err := InitializeGroups()
 		err := InitializeGroups()

+ 57 - 23
logic/zombie.go

@@ -2,7 +2,6 @@ package logic
 
 
 import (
 import (
 	"context"
 	"context"
-	"net"
 	"time"
 	"time"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -18,15 +17,16 @@ const (
 )
 )
 
 
 var (
 var (
-	zombies      []uuid.UUID
-	removeZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
-	newZombie    chan uuid.UUID = make(chan (uuid.UUID), 10)
+	zombies       []uuid.UUID
+	hostZombies   []uuid.UUID
+	newZombie     chan uuid.UUID = make(chan (uuid.UUID), 10)
+	newHostZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
 )
 )
 
 
-// CheckZombies - checks if new node has same macaddress as existing node
+// CheckZombies - checks if new node has same hostid as existing node
 // if so, existing node is added to zombie node quarantine list
 // if so, existing node is added to zombie node quarantine list
 // also cleans up nodes past their expiration date
 // also cleans up nodes past their expiration date
-func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
+func CheckZombies(newnode *models.Node) {
 	nodes, err := GetNetworkNodes(newnode.Network)
 	nodes, err := GetNetworkNodes(newnode.Network)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "Failed to retrieve network nodes", newnode.Network, err.Error())
 		logger.Log(1, "Failed to retrieve network nodes", newnode.Network, err.Error())
@@ -44,6 +44,35 @@ func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
 	}
 	}
 }
 }
 
 
+// checkForZombieHosts - checks if new host has the same macAddress as an existing host
+// if true, existing host is added to host zombie collection
+func checkForZombieHosts(h *models.Host) {
+	hosts, err := GetAllHosts()
+	if err != nil {
+		logger.Log(3, "errror retrieving all hosts", err.Error())
+	}
+	for _, existing := range hosts {
+		if existing.ID == h.ID {
+			//probably an unnecessary check as new host should not be in database yet, but just in case
+			//skip self
+			continue
+		}
+		if existing.MacAddress.String() == h.MacAddress.String() {
+			//add to hostZombies
+			newHostZombie <- existing.ID
+			//add all nodes belonging to host to zombile list
+			for _, node := range existing.Nodes {
+				id, err := uuid.Parse(node)
+				if err != nil {
+					logger.Log(3, "error parsing uuid from host.Nodes", err.Error())
+					continue
+				}
+				newHostZombie <- id
+			}
+		}
+	}
+}
+
 // ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
 // ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
 	logger.Log(2, "Zombie management started")
@@ -51,24 +80,12 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	for {
 	for {
 		select {
 		select {
 		case <-ctx.Done():
 		case <-ctx.Done():
+			close(peerUpdate)
 			return
 			return
 		case id := <-newZombie:
 		case id := <-newZombie:
-			logger.Log(1, "adding", id.String(), "to zombie quaratine list")
 			zombies = append(zombies, id)
 			zombies = append(zombies, id)
-		case id := <-removeZombie:
-			found := false
-			if len(zombies) > 0 {
-				for i := len(zombies) - 1; i >= 0; i-- {
-					if zombies[i] == id {
-						logger.Log(1, "removing zombie from quaratine list", zombies[i].String())
-						zombies = append(zombies[:i], zombies[i+1:]...)
-						found = true
-					}
-				}
-			}
-			if !found {
-				logger.Log(3, "no zombies found")
-			}
+		case id := <-newHostZombie:
+			hostZombies = append(hostZombies, id)
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 			logger.Log(3, "checking for zombie nodes")
 			logger.Log(3, "checking for zombie nodes")
 			if len(zombies) > 0 {
 			if len(zombies) > 0 {
@@ -92,6 +109,23 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 					}
 					}
 				}
 				}
 			}
 			}
+			if len(hostZombies) > 0 {
+				logger.Log(3, "checking host zombies")
+				for i := len(hostZombies) - 1; i >= 0; i-- {
+					host, err := GetHost(hostZombies[i].String())
+					if err != nil {
+						logger.Log(1, "error retrieving zombie host", err.Error())
+						logger.Log(1, "deleting ", host.ID.String(), " from zombie list")
+						zombies = append(zombies[:i], zombies[i+1:]...)
+						continue
+					}
+					if len(host.Nodes) == 0 {
+						if err := RemoveHost(host); err != nil {
+							logger.Log(0, "error deleting zombie host", host.ID.String(), err.Error())
+						}
+					}
+				}
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -115,10 +149,10 @@ func InitializeZombies() {
 			}
 			}
 			if node.HostID == othernode.HostID {
 			if node.HostID == othernode.HostID {
 				if node.LastCheckIn.After(othernode.LastCheckIn) {
 				if node.LastCheckIn.After(othernode.LastCheckIn) {
-					zombies = append(zombies, othernode.ID)
+					newZombie <- othernode.ID
 					logger.Log(1, "adding", othernode.ID.String(), "to zombie list")
 					logger.Log(1, "adding", othernode.ID.String(), "to zombie list")
 				} else {
 				} else {
-					zombies = append(zombies, node.ID)
+					newZombie <- node.ID
 					logger.Log(1, "adding", node.ID.String(), "to zombie list")
 					logger.Log(1, "adding", node.ID.String(), "to zombie list")
 				}
 				}
 			}
 			}

+ 39 - 0
models/dnsEntry.go

@@ -1,6 +1,45 @@
 // TODO:  Either add a returnNetwork and returnKey, or delete this
 // TODO:  Either add a returnNetwork and returnKey, or delete this
 package models
 package models
 
 
+// DNSUpdateAction identifies the action to be performed with the dns update data
+type DNSUpdateAction int
+
+const (
+	// DNSDeleteByIP delete the dns entry
+	DNSDeleteByIP = iota
+	// DNSDeleteByName delete the dns entry
+	DNSDeleteByName
+	// DNSReplaceName replace the dns entry
+	DNSReplaceName
+	// DNSReplaceIP resplace the dns entry
+	DNSReplaceIP
+	// DNSInsert insert a new dns entry
+	DNSInsert
+)
+
+func (action DNSUpdateAction) String() string {
+	return [...]string{"DNSDeleteByIP", "DNSDeletByName", "DNSReplaceName", "DNSReplaceIP", "DNSInsert"}[action]
+}
+
+// DNSError.Error implementation of error interface
+func (e DNSError) Error() string {
+	return "error publishing dns update"
+}
+
+// DNSError error struct capable of holding multiple error messages
+type DNSError struct {
+	ErrorStrings []string
+}
+
+// DNSUpdate data for updating entries in /etc/hosts
+type DNSUpdate struct {
+	Action     DNSUpdateAction
+	Name       string
+	NewName    string
+	Address    string
+	NewAddress string
+}
+
 // DNSEntry - a DNS entry represented as struct
 // DNSEntry - a DNS entry represented as struct
 type DNSEntry struct {
 type DNSEntry struct {
 	Address  string `json:"address" bson:"address" validate:"ip"`
 	Address  string `json:"address" bson:"address" validate:"ip"`

+ 11 - 17
models/mqtt.go

@@ -12,28 +12,27 @@ type PeerUpdate struct {
 	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
 	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
 	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
 	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
 	Peers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
 	Peers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
-	DNS           string               `json:"dns" bson:"dns" yaml:"dns"`
 	PeerIDs       PeerMap              `json:"peerids" bson:"peerids" yaml:"peerids"`
 	PeerIDs       PeerMap              `json:"peerids" bson:"peerids" yaml:"peerids"`
 	ProxyUpdate   ProxyManagerPayload  `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
 	ProxyUpdate   ProxyManagerPayload  `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
 }
 }
 
 
 // HostPeerUpdate - struct for host peer updates
 // HostPeerUpdate - struct for host peer updates
 type HostPeerUpdate struct {
 type HostPeerUpdate struct {
-	Host          Host                   `json:"host" bson:"host" yaml:"host"`
-	Server        string                 `json:"server" bson:"server" yaml:"server"`
-	ServerVersion string                 `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	ServerAddrs   []ServerAddr           `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
-	Network       map[string]NetworkInfo `json:"network" bson:"network" yaml:"network"`
-	Peers         []wgtypes.PeerConfig   `json:"peers" bson:"peers" yaml:"peers"`
-	PeerIDs       HostPeerMap            `json:"peerids" bson:"peerids" yaml:"peerids"`
-	ProxyUpdate   ProxyManagerPayload    `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
-	EgressInfo    map[string]EgressInfo  `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
-	IngressInfo   IngressInfo            `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	Host          Host                  `json:"host" bson:"host" yaml:"host"`
+	Server        string                `json:"server" bson:"server" yaml:"server"`
+	ServerVersion string                `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	ServerAddrs   []ServerAddr          `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	Peers         []wgtypes.PeerConfig  `json:"peers" bson:"peers" yaml:"peers"`
+	PeerIDs       HostPeerMap           `json:"peerids" bson:"peerids" yaml:"peerids"`
+	ProxyUpdate   ProxyManagerPayload   `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
+	EgressInfo    map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
+	IngressInfo   IngressInfo           `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
 }
 }
 
 
 // IngressInfo - struct for ingress info
 // IngressInfo - struct for ingress info
 type IngressInfo struct {
 type IngressInfo struct {
-	ExtPeers map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	ExtPeers     map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	EgressRanges []string                 `json:"egress_ranges" yaml:"egress_ranges"`
 }
 }
 
 
 // EgressInfo - struct for egress info
 // EgressInfo - struct for egress info
@@ -62,11 +61,6 @@ type ExtClientInfo struct {
 	Peers       map[string]PeerRouteInfo `json:"peers" yaml:"peers"`
 	Peers       map[string]PeerRouteInfo `json:"peers" yaml:"peers"`
 }
 }
 
 
-// NetworkInfo - struct for network info
-type NetworkInfo struct {
-	DNS string `json:"dns" bson:"dns" yaml:"dns"`
-}
-
 // KeyUpdate - key update struct
 // KeyUpdate - key update struct
 type KeyUpdate struct {
 type KeyUpdate struct {
 	Network   string `json:"network" bson:"network"`
 	Network   string `json:"network" bson:"network"`

+ 1 - 1
models/network_test.go

@@ -2,7 +2,7 @@ package models
 
 
 // moved from controllers need work
 // moved from controllers need work
 //func TestUpdateNetwork(t *testing.T) {
 //func TestUpdateNetwork(t *testing.T) {
-//	database.InitializeDatabase()
+//	initialize()
 //	createNet()
 //	createNet()
 //	network := getNet()
 //	network := getNet()
 //	t.Run("NetID", func(t *testing.T) {
 //	t.Run("NetID", func(t *testing.T) {

+ 272 - 75
mq/publishers.go

@@ -56,7 +56,7 @@ func PublishSingleHostUpdate(host *models.Host) error {
 	return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
 	return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
 }
 }
 
 
-// PublishPeerUpdate --- publishes a peer update to all the peers of a node
+// PublishExtPeerUpdate --- publishes a peer update to all the peers of a node
 func PublishExtPeerUpdate(node *models.Node) error {
 func PublishExtPeerUpdate(node *models.Node) error {
 
 
 	go PublishPeerUpdate()
 	go PublishPeerUpdate()
@@ -83,7 +83,7 @@ func NodeUpdate(node *models.Node) error {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		logger.Log(2, "error marshalling node update ", err.Error())
 		return err
 		return err
 	}
 	}
-	if err = publish(host, fmt.Sprintf("update/%s/%s", node.Network, node.ID), data); err != nil {
+	if err = publish(host, fmt.Sprintf("node/update/%s/%s", node.Network, node.ID), data); err != nil {
 		logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
 		logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
 		return err
 		return err
 	}
 	}
@@ -111,97 +111,195 @@ func HostUpdate(hostUpdate *models.HostUpdate) error {
 	return nil
 	return nil
 }
 }
 
 
-// sendPeers - retrieve networks, send peer ports to all peers
-func sendPeers() {
-
-	hosts, err := logic.GetAllHosts()
+// ServerStartNotify - notifies all non server nodes to pull changes after a restart
+func ServerStartNotify() error {
+	nodes, err := logic.GetAllNodes()
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+		return err
+	}
+	for i := range nodes {
+		nodes[i].Action = models.NODE_FORCE_UPDATE
+		if err = NodeUpdate(&nodes[i]); err != nil {
+			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+		}
 	}
 	}
+	return nil
+}
 
 
-	var force bool
-	peer_force_send++
-	if peer_force_send == 5 {
-		servercfg.SetHost()
-		force = true
-		peer_force_send = 0
-		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+// PublishDNSUpdate publishes a dns update to all nodes on a network
+func PublishDNSUpdate(network string, dns models.DNSUpdate) error {
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		return err
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
 		if err != nil {
-			logger.Log(3, "error occurred on timer,", err.Error())
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		data, err := json.Marshal(dns)
+		if err != nil {
+			logger.Log(0, "failed to encode dns data for node", node.ID.String(), err.Error())
 		}
 		}
+		if err := publish(host, "dns/update/"+host.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+			logger.Log(0, "error publishing dns update to host", host.ID.String(), err.Error())
+			continue
+		}
+		logger.Log(3, "published dns update to host", host.ID.String())
+	}
+	return nil
+}
 
 
-		//collectServerMetrics(networks[:])
+// PublishAllDNS publishes an array of dns updates (ip / host.network) for each peer to a node joining a network
+func PublishAllDNS(newnode *models.Node) error {
+	alldns := []models.DNSUpdate{}
+	newnodeHost, err := logic.GetHost(newnode.HostID.String())
+	if err != nil {
+		return fmt.Errorf("error retrieving host for dns update %w", err)
+	}
+	alldns = append(alldns, getNodeDNS(newnode.Network)...)
+	alldns = append(alldns, getExtClientDNS(newnode.Network)...)
+	alldns = append(alldns, getCustomDNS(newnode.Network)...)
+	data, err := json.Marshal(alldns)
+	if err != nil {
+		return fmt.Errorf("error encoding dns data %w", err)
+	}
+	if err := publish(newnodeHost, "dns/all/"+newnodeHost.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+		return fmt.Errorf("error publishing full dns update to %s, %w", newnodeHost.ID.String(), err)
 	}
 	}
+	logger.Log(3, "published full dns update to %s", newnodeHost.ID.String())
+	return nil
+}
 
 
-	for _, host := range hosts {
-		if force {
-			host := host
-			logger.Log(2, "sending scheduled peer update (5 min)")
-			err = PublishSingleHostUpdate(&host)
-			if err != nil {
-				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
-			}
+// PublishDNSDelete publish a dns update deleting a node to all hosts on a network
+func PublishDNSDelete(node *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByIP,
+		Name:   host.Name + "." + node.Network,
+	}
+	if node.Address.IP != nil {
+		dns.Address = node.Address.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
+		}
+	}
+	if node.Address6.IP != nil {
+		dns.Address = node.Address6.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
 		}
 		}
 	}
 	}
+	return nil
 }
 }
 
 
-// ServerStartNotify - notifies all non server nodes to pull changes after a restart
-func ServerStartNotify() error {
-	nodes, err := logic.GetAllNodes()
-	if err != nil {
-		return err
+// PublishReplaceDNS publish a dns update to replace a dns entry on all hosts in network
+func PublishReplaceDNS(oldNode, newNode *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSReplaceIP,
+		Name:   host.Name + "." + oldNode.Network,
 	}
 	}
-	for i := range nodes {
-		nodes[i].Action = models.NODE_FORCE_UPDATE
-		if err = NodeUpdate(&nodes[i]); err != nil {
-			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+	if !oldNode.Address.IP.Equal(newNode.Address.IP) {
+		dns.Address = oldNode.Address.IP.String()
+		dns.NewAddress = newNode.Address.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
+		}
+	}
+	if !oldNode.Address6.IP.Equal(newNode.Address6.IP) {
+		dns.Address = oldNode.Address6.IP.String()
+		dns.NewAddress = newNode.Address6.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
 		}
 		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// function to collect and store metrics for server nodes
-//func collectServerMetrics(networks []models.Network) {
-//	if !servercfg.Is_EE {
-//		return
-//	}
-//	if len(networks) > 0 {
-//		for i := range networks {
-//			currentNetworkNodes, err := logic.GetNetworkNodes(networks[i].NetID)
-//			if err != nil {
-//				continue
-//			}
-//			currentServerNodes := logic.GetServerNodes(networks[i].NetID)
-//			if len(currentServerNodes) > 0 {
-//				for i := range currentServerNodes {
-//					if logic.IsLocalServer(&currentServerNodes[i]) {
-//						serverMetrics := logic.CollectServerMetrics(currentServerNodes[i].ID, currentNetworkNodes)
-//						if serverMetrics != nil {
-//							serverMetrics.NodeName = currentServerNodes[i].Name
-//							serverMetrics.NodeID = currentServerNodes[i].ID
-//							serverMetrics.IsServer = "yes"
-//							serverMetrics.Network = currentServerNodes[i].Network
-//							if err = metrics.GetExchangedBytesForNode(&currentServerNodes[i], serverMetrics); err != nil {
-//								logger.Log(1, fmt.Sprintf("failed to update exchanged bytes info for server: %s, err: %v",
-//									currentServerNodes[i].Name, err))
-//							}
-//							updateNodeMetrics(&currentServerNodes[i], serverMetrics)
-//							if err = logic.UpdateMetrics(currentServerNodes[i].ID, serverMetrics); err != nil {
-//								logger.Log(1, "failed to update metrics for server node", currentServerNodes[i].ID)
-//							}
-//							if servercfg.IsMetricsExporter() {
-//								logger.Log(2, "-------------> SERVER METRICS: ", fmt.Sprintf("%+v", serverMetrics))
-//								if err := pushMetricsToExporter(*serverMetrics); err != nil {
-//									logger.Log(2, "failed to push server metrics to exporter: ", err.Error())
-//								}
-//							}
-//						}
-//					}
-//				}
-//			}
-//		}
-//	}
-//}
+// PublishExtClientDNS publish dns update for new extclient
+func PublishExtCLientDNS(client *models.ExtClient) error {
+	errMsgs := models.DNSError{}
+	dns := models.DNSUpdate{
+		Action:  models.DNSInsert,
+		Name:    client.ClientID + "." + client.Network,
+		Address: client.Address,
+	}
+	if client.Address != "" {
+		dns.Address = client.Address
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+
+	}
+	if client.Address6 != "" {
+		dns.Address = client.Address6
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
+
+// PublishExtClientDNSUpdate update for extclient name change
+func PublishExtClientDNSUpdate(old, new models.ExtClient, network string) error {
+	dns := models.DNSUpdate{
+		Action:  models.DNSReplaceName,
+		Name:    old.ClientID + "." + network,
+		NewName: new.ClientID + "." + network,
+	}
+	if err := PublishDNSUpdate(network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishDeleteExtClientDNS publish dns update to delete extclient entry
+func PublishDeleteExtClientDNS(client *models.ExtClient) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByName,
+		Name:   client.ClientID + "." + client.Network,
+	}
+	if err := PublishDNSUpdate(client.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishCustomDNS publish dns update for new custom dns entry
+func PublishCustomDNS(entry *models.DNSEntry) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSInsert,
+		Name:   entry.Name + "." + entry.Network,
+		//entry.Address6 is never used
+		Address: entry.Address,
+	}
+	if err := PublishDNSUpdate(entry.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishHostDNSUpdate publishes dns update on host name change
+func PublishHostDNSUpdate(old, new *models.Host, networks []string) error {
+	errMsgs := models.DNSError{}
+	for _, network := range networks {
+		dns := models.DNSUpdate{
+			Action:  models.DNSReplaceName,
+			Name:    old.Name + "." + network,
+			NewName: new.Name + "." + network,
+		}
+		if err := PublishDNSUpdate(network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
 
 
 func pushMetricsToExporter(metrics models.Metrics) error {
 func pushMetricsToExporter(metrics models.Metrics) error {
 	logger.Log(2, "----> Pushing metrics to exporter")
 	logger.Log(2, "----> Pushing metrics to exporter")
@@ -220,3 +318,102 @@ func pushMetricsToExporter(metrics models.Metrics) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func getNodeDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		logger.Log(0, "error retreiving network nodes for network", network, err.Error())
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		dns.Action = models.DNSInsert
+		dns.Name = host.Name + "." + node.Network
+		if node.Address.IP != nil {
+			dns.Address = node.Address.IP.String()
+			alldns = append(alldns, dns)
+		}
+		if node.Address6.IP != nil {
+			dns.Address = node.Address6.IP.String()
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getExtClientDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	clients, err := logic.GetNetworkExtClients(network)
+	if err != nil {
+		logger.Log(0, "error retrieving extclients", err.Error())
+	}
+	for _, client := range clients {
+		dns.Action = models.DNSInsert
+		dns.Name = client.ClientID + "." + client.Network
+		if client.Address != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+		if client.Address6 != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getCustomDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	customdns, err := logic.GetCustomDNS(network)
+	if err != nil {
+		logger.Log(0, "error retrieving custom dns entries", err.Error())
+	}
+	for _, custom := range customdns {
+		dns.Action = models.DNSInsert
+		dns.Address = custom.Address
+		dns.Name = custom.Name + "." + custom.Network
+		alldns = append(alldns, dns)
+	}
+	return alldns
+}
+
+// sendPeers - retrieve networks, send peer ports to all peers
+func sendPeers() {
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+	}
+
+	var force bool
+	peer_force_send++
+	if peer_force_send == 5 {
+		servercfg.SetHost()
+		force = true
+		peer_force_send = 0
+		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+		if err != nil {
+			logger.Log(3, "error occurred on timer,", err.Error())
+		}
+
+		//collectServerMetrics(networks[:])
+	}
+
+	for _, host := range hosts {
+		if force {
+			host := host
+			logger.Log(2, "sending scheduled peer update (5 min)")
+			err = PublishSingleHostUpdate(&host)
+			if err != nil {
+				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
+			}
+		}
+	}
+}

+ 4 - 1
netclient/ncutils/netclientutils.go

@@ -166,13 +166,16 @@ func GetPublicIP(api string) (string, error) {
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
-		defer resp.Body.Close()
 		if resp.StatusCode == http.StatusOK {
 		if resp.StatusCode == http.StatusOK {
 			var bodyBytes []byte
 			var bodyBytes []byte
 			bodyBytes, err = io.ReadAll(resp.Body)
 			bodyBytes, err = io.ReadAll(resp.Body)
 			if err != nil {
 			if err != nil {
+				if resp.Body != nil {
+					_ = resp.Body.Close()
+				}
 				continue
 				continue
 			}
 			}
+			_ = resp.Body.Close()
 			endpoint = string(bodyBytes)
 			endpoint = string(bodyBytes)
 			break
 			break
 		}
 		}

+ 1 - 0
scripts/nm-quick.sh

@@ -29,6 +29,7 @@ fi
 unset INSTALL_TYPE
 unset INSTALL_TYPE
 unset BUILD_TYPE
 unset BUILD_TYPE
 unset BUILD_TAG
 unset BUILD_TAG
+unset IMAGE_TAG
 
 
 usage () {(
 usage () {(
     echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"
     echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"

+ 0 - 2
stun-server/stun-server.go

@@ -3,7 +3,6 @@ package stunserver
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"log"
 	"net"
 	"net"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
@@ -71,7 +70,6 @@ func (s *Server) serveConn(c net.PacketConn, res, req *stun.Message) error {
 		logger.Log(1, "ReadFrom: %v", err.Error())
 		logger.Log(1, "ReadFrom: %v", err.Error())
 		return nil
 		return nil
 	}
 	}
-	log.Printf("read %d bytes from %s\n", n, addr)
 	if _, err = req.Write(buf[:n]); err != nil {
 	if _, err = req.Write(buf[:n]); err != nil {
 		logger.Log(1, "Write: %v", err.Error())
 		logger.Log(1, "Write: %v", err.Error())
 		return err
 		return err