Browse Source

merge conflicts resolved

Abhishek Kondur 2 years ago
parent
commit
5b7ead91e6

+ 2 - 1
compose/docker-compose.yml

@@ -19,7 +19,8 @@ services:
       - sqldata:/root/data
       - mosquitto_data:/etc/netmaker
     environment:
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"

+ 5 - 4
config/config.go

@@ -1,7 +1,7 @@
-//Environment file for getting variables
-//Currently the only thing it does is set the master password
-//Should probably have it take over functions from OS such as port and mongodb connection details
-//Reads from the config/environments/dev.yaml file by default
+// Environment file for getting variables
+// Currently the only thing it does is set the master password
+// Should probably have it take over functions from OS such as port and mongodb connection details
+// Reads from the config/environments/dev.yaml file by default
 package config
 
 import (
@@ -69,6 +69,7 @@ type ServerConfig struct {
 	MQPort                string `yaml:"mqport"`
 	MQServerPort          string `yaml:"mqserverport"`
 	Server                string `yaml:"server"`
+	Broker                string `yam:"broker"`
 	PublicIPService       string `yaml:"publicipservice"`
 	MQAdminPassword       string `yaml:"mqadminpassword"`
 	MetricsExporter       string `yaml:"metrics_exporter"`

+ 102 - 103
controllers/node.go

@@ -2,6 +2,7 @@ package controller
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -40,13 +41,13 @@ func nodeHandlers(r *mux.Router) {
 //
 // Authenticate to make further API calls related to a network.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: successResponse
+//			Responses:
+//				200: successResponse
 func authenticate(response http.ResponseWriter, request *http.Request) {
 
 	var authRequest models.AuthParams
@@ -343,13 +344,13 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 //
 // Gets all nodes associated with network including pending nodes.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeSliceResponse
+//			Responses:
+//				200: nodeSliceResponse
 func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -382,13 +383,14 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 //
 // Get all nodes across all networks.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeSliceResponse
 //
-//		Responses:
-//			200: nodeSliceResponse
 // Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
 func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
@@ -439,13 +441,13 @@ func getUsersNodes(user models.User) ([]models.Node, error) {
 //
 // Get an individual node.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func getNode(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
@@ -504,13 +506,13 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 //
 // Create a node on a network.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeGetResponse
+//			Responses:
+//				200: nodeGetResponse
 func createNode(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -547,6 +549,12 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if !logic.IsVersionComptatible(node.Version) {
+		err := errors.New("incomatible netclient version")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
 	node.Network = networkName
 
 	network, err := logic.GetNetworkByNode(&node)
@@ -631,7 +639,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 		if !updatedUserNode { // user was found but not updated, so delete node
 			logger.Log(0, "failed to add node to user", keyName)
-			logic.DeleteNodeByID(&node, true)
+			logic.DeleteNode(&node, true)
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 			return
 		}
@@ -645,12 +653,12 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	// Create client for this node in Mq
+	// Create client for this host in Mq
 	event := mq.MqDynsecPayload{
 		Commands: []mq.MqDynSecCmd{
 			{ // delete if any client exists already
 				Command:  mq.DeleteClientCmd,
-				Username: node.ID,
+				Username: node.HostID,
 			},
 			{
 				Command:  mq.CreateRoleCmd,
@@ -660,7 +668,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			},
 			{
 				Command:  mq.CreateClientCmd,
-				Username: node.ID,
+				Username: node.HostID,
 				Password: nodePassword,
 				Textname: node.Name,
 				Roles: []mq.MqDynSecRole{
@@ -700,13 +708,14 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 //
 // Takes a node out of pending state.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
 //
-//		Responses:
-//			200: nodeResponse
 // Takes node out of pending state
 // TODO: May want to use cordon/uncordon terminology instead of "ispending".
 func uncordonNode(w http.ResponseWriter, r *http.Request) {
@@ -733,13 +742,13 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) {
 //
 // Create an egress gateway.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 	var gateway models.EgressGatewayRequest
 	var params = mux.Vars(r)
@@ -772,13 +781,13 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 //
 // Delete an egress gateway.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
@@ -806,13 +815,13 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 //
 // Create an ingress gateway.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	w.Header().Set("Content-Type", "application/json")
@@ -850,13 +859,13 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 //
 // Delete an ingress gateway.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
@@ -888,13 +897,13 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 //
 // Update an individual node.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func updateNode(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -998,13 +1007,13 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 //
 // Delete an individual node.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: nodeResponse
+//			Responses:
+//				200: nodeResponse
 func deleteNode(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
@@ -1013,22 +1022,11 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var nodeid = params["nodeid"]
 	fromNode := r.Header.Get("requestfrom") == "node"
-	var node, err = logic.GetNodeByID(nodeid)
+	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
-		if fromNode {
-			node, err = logic.GetDeletedNodeByID(nodeid)
-			if err != nil {
-				logger.Log(0, r.Header.Get("user"),
-					fmt.Sprintf("error fetching node from deleted nodes [ %s ] info: %v", nodeid, err))
-				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-				return
-			}
-		} else {
-			logger.Log(0, r.Header.Get("user"),
-				fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err))
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-			return
-		}
+		logger.Log(0, "error retrieving node to delete", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
 	}
 	if isServer(&node) {
 		err := fmt.Errorf("cannot delete server node")
@@ -1044,34 +1042,35 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	//send update to node to be deleted before deleting on server otherwise message cannot be sent
-	node.Action = models.NODE_DELETE
-
-	err = logic.DeleteNodeByID(&node, fromNode)
-	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+	if err := logic.DeleteNode(&node, fromNode); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 	}
 	if fromNode {
-		// deletes node related role and client
-		event := mq.MqDynsecPayload{
-			Commands: []mq.MqDynSecCmd{
-				{
-					Command:  mq.DeleteClientCmd,
-					Username: nodeid,
-				},
-			},
-		}
-
-		if err := mq.PublishEventToDynSecTopic(event); err != nil {
-			logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
-				event.Commands, err.Error()))
+		//check if server should be removed from mq
+		found := false
+		// err is irrelevent
+		nodes, _ := logic.GetAllNodes()
+		for _, nodetocheck := range nodes {
+			if nodetocheck.HostID == node.HostID {
+				found = true
+				break
+			}
 		}
-	}
-
-	if servercfg.Is_EE {
-		if err = logic.EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil {
-			logger.Log(0, "failed to reset failover lists during node delete for node", node.Name, node.Network)
+		if !found {
+			// deletes node related role and client
+			event := mq.MqDynsecPayload{
+				Commands: []mq.MqDynSecCmd{
+					{
+						Command:  mq.DeleteClientCmd,
+						Username: node.HostID,
+					},
+				},
+			}
+			if err := mq.PublishEventToDynSecTopic(event); err != nil {
+				logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+					event.Commands, err.Error()))
+			}
 		}
 	}
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")

+ 3 - 0
go.mod

@@ -50,6 +50,8 @@ require (
 	gortc.io/stun v1.23.0
 )
 
+require github.com/matryer/is v1.4.0
+
 require (
 	cloud.google.com/go/compute v1.7.0 // indirect
 	fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect
@@ -77,6 +79,7 @@ require (
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
+	github.com/hashicorp/go-version v1.6.0
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
 	github.com/kr/text v0.2.0 // indirect

+ 4 - 0
go.sum

@@ -301,6 +301,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -341,6 +343,8 @@ github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=

+ 4 - 0
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,11 @@ spec:
       hostNetwork: true
       containers:
       - name: netclient
+<<<<<<< HEAD
         image: gravitl/netclient:v0.17.0
+=======
+        image: gravitl/netclient:v0.16.3
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

+ 4 - 0
k8s/client/netclient.yaml

@@ -28,7 +28,11 @@ spec:
       #           - "<node label value>"
       containers:
       - name: netclient
+<<<<<<< HEAD
         image: gravitl/netclient:v0.17.0
+=======
+        image: gravitl/netclient:v0.16.3
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

+ 4 - 0
k8s/server/netmaker-server.yaml

@@ -83,7 +83,11 @@ spec:
           value: "Kubernetes"
         - name: VERBOSITY
           value: "3"
+<<<<<<< HEAD
         image: gravitl/netmaker:v0.17.0
+=======
+        image: gravitl/netmaker:v0.16.3
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
         imagePullPolicy: Always
         name: netmaker
         ports:

+ 4 - 0
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,11 @@ spec:
     spec:
       containers:
       - name: netmaker-ui
+<<<<<<< HEAD
         image: gravitl/netmaker-ui:v0.17.0
+=======
+        image: gravitl/netmaker-ui:v0.16.3
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
         ports:
         - containerPort: 443
         env:

+ 1 - 1
logic/networks.go

@@ -54,7 +54,7 @@ func DeleteNetwork(network string) error {
 		servers, err := GetSortedNetworkServerNodes(network)
 		if err == nil {
 			for _, s := range servers {
-				if err = DeleteNodeByID(&s, true); err != nil {
+				if err = DeleteNode(&s, true); err != nil {
 					logger.Log(2, "could not removed server", s.Name, "before deleting network", network)
 				} else {
 					logger.Log(2, "removed server", s.Name, "before deleting network", network)

+ 67 - 23
logic/nodes.go

@@ -1,6 +1,7 @@
 package logic
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -22,8 +23,14 @@ import (
 	"golang.org/x/crypto/bcrypt"
 )
 
-// RELAY_NODE_ERR - error to return if relay node is unfound
-const RELAY_NODE_ERR = "could not find relay for node"
+const (
+	// RELAY_NODE_ERR - error to return if relay node is unfound
+	RELAY_NODE_ERR = "could not find relay for node"
+	// NodePurgeTime time to wait for node to response to a NODE_DELETE actions
+	NodePurgeTime = time.Second * 10
+	// NodePurgeCheckTime is how often to check nodes for Pending Delete
+	NodePurgeCheckTime = time.Second * 30
+)
 
 // GetNetworkNodes - gets the nodes of a network
 func GetNetworkNodes(network string) ([]models.Node, error) {
@@ -160,8 +167,31 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 	return fmt.Errorf("failed to update node " + currentNode.ID + ", cannot change ID.")
 }
 
-// DeleteNodeByID - deletes a node from database or moves into delete nodes table
-func DeleteNodeByID(node *models.Node, exterminate bool) error {
+// DeleteNode - marks node for deletion if called by UI or deletes node if called by node
+func DeleteNode(node *models.Node, purge bool) error {
+	if !purge {
+		newnode := node
+		newnode.PendingDelete = true
+		newnode.Action = models.NODE_DELETE
+		if err := UpdateNode(node, newnode); err != nil {
+			return err
+		}
+		return nil
+	}
+	if err := DeleteNodeByID(node); err != nil {
+		return err
+	}
+	if servercfg.Is_EE {
+		if err := EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil {
+			logger.Log(0, "failed to reset failover lists during node delete for node", node.Name, node.Network)
+		}
+	}
+
+	return nil
+}
+
+// DeleteNodeByID - deletes a node from database
+func DeleteNodeByID(node *models.Node) error {
 	var err error
 	var key = node.ID
 	//delete any ext clients as required
@@ -170,27 +200,11 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error {
 			logger.Log(0, "failed to deleted ext clients", err.Error())
 		}
 	}
-	if !exterminate {
-		node.Action = models.NODE_DELETE
-		nodedata, err := json.Marshal(&node)
-		if err != nil {
-			return err
-		}
-		err = database.Insert(key, string(nodedata), database.DELETED_NODES_TABLE_NAME)
-		if err != nil {
-			return err
-		}
-	} else {
-		if err := database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, key); err != nil {
-			logger.Log(2, err.Error())
-		}
-	}
 	if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil {
 		if !database.IsEmptyRecord(err) {
 			return err
 		}
 	}
-
 	if servercfg.IsDNSMode() {
 		SetDNS()
 	}
@@ -200,7 +214,6 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error {
 			logger.Log(0, "failed to dissasociate", node.OwnerID, "from node", node.ID, ":", err.Error())
 		}
 	}
-
 	_, err = nodeacls.RemoveNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID))
 	if err != nil {
 		// ignoring for now, could hit a nil pointer if delete called twice
@@ -210,11 +223,9 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error {
 	if err = DeleteMetrics(node.ID); err != nil {
 		logger.Log(1, "unable to remove metrics from DB for node", node.ID, err.Error())
 	}
-
 	if node.IsServer == "yes" {
 		return removeLocalServer(node)
 	}
-
 	return nil
 }
 
@@ -324,6 +335,9 @@ func CreateNode(node *models.Node) error {
 	}
 
 	node.ID = uuid.NewString()
+	if node.IsServer == "yes" {
+		node.HostID = uuid.NewString()
+	}
 
 	//Create a JWT for the node
 	tokenString, _ := CreateJWT(node.ID, node.MacAddress, node.Network)
@@ -770,4 +784,34 @@ func updateProNodeACLS(node *models.Node) error {
 	return nil
 }
 
+func PurgePendingNodes(ctx context.Context) {
+	ticker := time.NewTicker(NodePurgeCheckTime)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			nodes, err := GetAllNodes()
+			if err != nil {
+				logger.Log(0, "PurgePendingNodes failed to retrieve nodes", err.Error())
+				continue
+			}
+			for _, node := range nodes {
+				if node.PendingDelete {
+					modified := time.Unix(node.LastModified, 0)
+					if time.Since(modified) > NodePurgeTime {
+						if err := DeleteNode(&node, true); err != nil {
+							logger.Log(0, "failed to purge node", node.ID, err.Error())
+						} else {
+							logger.Log(0, "purged node ", node.ID)
+						}
+
+					}
+				}
+			}
+		}
+	}
+}
+
 // == END PRO ==

+ 2 - 0
logic/peers.go

@@ -257,6 +257,8 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) {
 				if peer.LocalListenPort != 0 {
 					peer.ListenPort = peer.LocalListenPort
 				}
+			} else {
+				continue
 			}
 		}
 

+ 2 - 2
logic/server.go

@@ -208,7 +208,7 @@ func ServerUpdate(serverNode *models.Node, ifaceDelta bool) error {
 
 	var err = ServerPull(serverNode, ifaceDelta)
 	if isDeleteError(err) {
-		return DeleteNodeByID(serverNode, true)
+		return DeleteNode(serverNode, true)
 	} else if err != nil && !ifaceDelta {
 		err = ServerPull(serverNode, true)
 		if err != nil {
@@ -239,7 +239,7 @@ func checkNodeActions(node *models.Node) string {
 		}
 	}
 	if node.Action == models.NODE_DELETE {
-		err := DeleteNodeByID(node, true)
+		err := DeleteNode(node, true)
 		if err != nil {
 			logger.Log(1, "error deleting locally:", err.Error())
 		}

+ 31 - 0
logic/version.go

@@ -0,0 +1,31 @@
+package logic
+
+import (
+	"strings"
+	"unicode"
+
+	"github.com/hashicorp/go-version"
+)
+
+const MinVersion = "v0.17.0"
+
+// IsVersionCompatible checks that the version passed is compabtible (>=) with MinVersion
+func IsVersionComptatible(ver string) bool {
+	// during dev, assume developers know what they are doing
+	if ver == "dev" {
+		return true
+	}
+	trimmed := strings.TrimFunc(ver, func(r rune) bool {
+		return !unicode.IsNumber(r)
+	})
+	v, err := version.NewVersion(trimmed)
+	if err != nil {
+		return false
+	}
+	constraint, err := version.NewConstraint(">= " + MinVersion)
+	if err != nil {
+		return false
+	}
+	return constraint.Check(v)
+
+}

+ 35 - 0
logic/version_test.go

@@ -0,0 +1,35 @@
+package logic
+
+import (
+	"testing"
+
+	"github.com/matryer/is"
+)
+
+func TestVersion(t *testing.T) {
+	t.Run("valid version", func(t *testing.T) {
+		is := is.New(t)
+		valid := IsVersionComptatible("v0.17.1-testing")
+		is.Equal(valid, true)
+	})
+	t.Run("dev version", func(t *testing.T) {
+		is := is.New(t)
+		valid := IsVersionComptatible("dev")
+		is.Equal(valid, true)
+	})
+	t.Run("invalid version", func(t *testing.T) {
+		is := is.New(t)
+		valid := IsVersionComptatible("v0.14.2-refactor")
+		is.Equal(valid, false)
+	})
+	t.Run("no version", func(t *testing.T) {
+		is := is.New(t)
+		valid := IsVersionComptatible("testing")
+		is.Equal(valid, false)
+	})
+	t.Run("incomplete version", func(t *testing.T) {
+		is := is.New(t)
+		valid := IsVersionComptatible("0.18")
+		is.Equal(valid, true)
+	})
+}

+ 1 - 1
logic/zombie.go

@@ -74,7 +74,7 @@ func ManageZombies(ctx context.Context) {
 						continue
 					}
 					if time.Since(time.Unix(node.LastCheckIn, 0)) > time.Minute*ZOMBIE_DELETE_TIME {
-						if err := DeleteNodeByID(&node, true); err != nil {
+						if err := DeleteNode(&node, true); err != nil {
 							logger.Log(1, "error deleting zombie node", zombies[i], err.Error())
 							continue
 						}

+ 1 - 0
main.go

@@ -209,6 +209,7 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	ctx, cancel := context.WithCancel(context.Background())
 	go mq.Keepalive(ctx)
 	go logic.ManageZombies(ctx)
+	go logic.PurgePendingNodes(ctx)
 	quit := make(chan os.Signal, 1)
 	signal.Notify(quit, syscall.SIGTERM, os.Interrupt)
 	<-quit

+ 10 - 0
models/node.go

@@ -43,14 +43,23 @@ var seededRand *rand.Rand = rand.New(
 type NodeCheckin struct {
 	Version   string
 	Connected string
+	Ifaces    []Iface
+}
+
+// Iface struct for local interfaces of a node
+type Iface struct {
+	Name    string
+	Address net.IPNet
 }
 
 // Node - struct for node model
 type Node struct {
 	ID                      string               `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"`
+	HostID                  string               `json:"hostid,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"`
 	Address                 string               `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
 	Address6                string               `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
 	LocalAddress            string               `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty"`
+	Interfaces              []Iface              `json:"interfaces" yaml:"interfaces"`
 	Name                    string               `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
 	NetworkSettings         Network              `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
 	ListenPort              int32                `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
@@ -102,6 +111,7 @@ type Node struct {
 	FirewallInUse   string      `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"`
 	InternetGateway string      `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"`
 	Connected       string      `json:"connected" bson:"connected" yaml:"connected" validate:"checkyesorno"`
+	PendingDelete   bool        `json:"pendingdelete" bson:"pendingdelete" yaml:"pendingdelete"`
 	// == PRO ==
 	DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	OwnerID    string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`

+ 1 - 0
models/structs.go

@@ -220,6 +220,7 @@ type ServerConfig struct {
 	Version     string `yaml:"version"`
 	MQPort      string `yaml:"mqport"`
 	Server      string `yaml:"server"`
+	Broker      string `yaml:"broker"`
 	Is_EE       bool   `yaml:"isee"`
 	StunPort    string `yaml:"stun_port"`
 }

+ 1 - 0
mq/handlers.go

@@ -52,6 +52,7 @@ func Ping(client mqtt.Client, msg mqtt.Message) {
 		node.SetLastCheckIn()
 		node.Version = checkin.Version
 		node.Connected = checkin.Connected
+		node.Interfaces = checkin.Ifaces
 		if err := logic.UpdateNode(&node, &node); err != nil {
 			logger.Log(0, "error updating node", node.Name, node.ID, " on checkin", err.Error())
 			return

+ 4 - 0
netclient/netclient.exe.manifest.xml

@@ -1,7 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
     <assemblyIdentity
+<<<<<<< HEAD
             version="0.17.0.0"
+=======
+            version="0.16.3.0"
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
             processorArchitecture="*"
             name="netclient.exe"
             type="win32"

+ 97 - 0
scripts/nm-quick-interactive.sh

@@ -17,11 +17,14 @@ cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 EOF
 
+<<<<<<< HEAD
 if [ $(id -u) -ne 0 ]; then
    echo "This script must be run as root"
    exit 1
 fi
 
+=======
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 if [ -z "$1" ]; then
 	echo "-----------------------------------------------------"
 	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
@@ -66,12 +69,24 @@ confirm() {(
       read -p 'Does everything look right? [y/n]: ' yn
       case $yn in
           [Yy]* ) override="true"; break;;
+<<<<<<< HEAD
           [Nn]* ) echo "exiting..."; exit 1;;
+=======
+          [Nn]* ) echo "exiting..."; exit;;
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
           * ) echo "Please answer yes or no.";;
       esac
   done
 )}
 
+<<<<<<< HEAD
+=======
+if [ $(id -u) -ne 0 ]; then
+   echo "This script must be run as root"
+   exit 1
+fi
+
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 echo "checking dependencies..."
 
 OS=$(uname)
@@ -124,9 +139,12 @@ if [ -z "${install_cmd}" ]; then
 fi
 
 set -- $dependencies
+<<<<<<< HEAD
 
 ${update_cmd}
 
+=======
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 while [ -n "$1" ]; do
 	if [ "${OS}" = "FreeBSD" ]; then
 		is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
@@ -189,12 +207,22 @@ COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
 SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+<<<<<<< HEAD
 DOMAIN_TYPE=""
 
 echo "-----------------------------------------------------"
 echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
 echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
 echo "-----------------------------------------------------"
+=======
+EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
+DOMAIN_TYPE=""
+
+echo "-----------------------------------------------------"
+echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
+echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
+echo "-----------------------------------------------------"
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do
   case $REPLY in
     1)
@@ -212,9 +240,15 @@ select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain
     *) echo "invalid option $REPLY";;
   esac
 done
+<<<<<<< HEAD
 
 wait_seconds 2
 
+=======
+
+wait_seconds 2
+
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 echo "-----------------------------------------------------"
 echo "The following subdomains will be used:"
 echo "          dashboard.$NETMAKER_BASE_DOMAIN"
@@ -225,6 +259,7 @@ if [ "$INSTALL_TYPE" = "ee" ]; then
 	echo "         prometheus.$NETMAKER_BASE_DOMAIN"
 	echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
 	echo "            grafana.$NETMAKER_BASE_DOMAIN"
+<<<<<<< HEAD
 fi
 
 echo "-----------------------------------------------------"
@@ -266,6 +301,45 @@ if [ -z "$GET_EMAIL" ]; then
 else
   EMAIL="$GET_EMAIL"
 fi
+=======
+fi
+
+echo "-----------------------------------------------------"
+
+if [[ "$DOMAIN_TYPE" == "custom" ]]; then
+	echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP"
+	confirm
+fi
+
+wait_seconds 1
+
+if [ "$INSTALL_TYPE" = "ee" ]; then
+
+	echo "-----------------------------------------------------"
+	echo "Provide Details for EE installation:"
+	echo "    1. Log into https://dashboard.license.netmaker.io"
+	echo "    2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys"
+	echo "    3. Retrieve Account ID: https://dashboard.license.netmaker.io/user"
+	echo "    4. note email address"
+	echo "-----------------------------------------------------"
+	unset LICENSE_KEY
+	while [ -z "$LICENSE_KEY" ]; do
+		read -p "License Key: " LICENSE_KEY
+	done
+	unset ACCOUNT_ID
+	while [ -z ${ACCOUNT_ID} ]; do
+		read -p "Account ID: " ACCOUNT_ID
+	done
+
+fi
+
+unset EMAIL
+while [ -z ${EMAIL} ]; do
+     read -p "Email Address (for LetsEncrypt): " EMAIL
+done
+
+wait_seconds 2
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 
 wait_seconds 2
 
@@ -295,6 +369,7 @@ wait_seconds 3
 echo "Pulling config files..."
 
 COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml" 
+<<<<<<< HEAD
 CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile"
 if [ "$INSTALL_TYPE" = "ee" ]; then
 	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" 
@@ -302,6 +377,13 @@ if [ "$INSTALL_TYPE" = "ee" ]; then
 fi
 
 wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL && wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh && chmod +x /root/wait.sh
+=======
+if [ "$INSTALL_TYPE" = "ee" ]; then
+	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" 
+fi
+
+wget -O docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/develop/docker/wait.sh && chmod +x wait.sh
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 
 mkdir -p /etc/netmaker
 
@@ -311,7 +393,11 @@ sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
 sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
 sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
 sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
+<<<<<<< HEAD
 sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
+=======
+sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/docker-compose.yml
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 sed -i "s/REPLACE_MQ_ADMIN_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml 
 if [ "$INSTALL_TYPE" = "ee" ]; then
 	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml 
@@ -368,6 +454,7 @@ wait_seconds 3
 
 echo "Configuring netmaker server as ingress gateway"
 
+<<<<<<< HEAD
 for i in 1 2 3 4 5 6
 do
 	echo "    waiting for server node to become available"
@@ -386,6 +473,16 @@ do
 	fi
 done
 
+=======
+
+while [ -z "$SERVER_ID" ]; do
+	echo "waiting for server node to become available"
+	wait_seconds 2
+	curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
+	SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
+done
+
+>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4
 curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
 
 )}

+ 13 - 1
servercfg/serverconf.go

@@ -96,6 +96,8 @@ func GetServerConfig() config.ServerConfig {
 // GetServerConfig - gets the server config into memory from file or env
 func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig
+	cfg.Server = GetServer()
+	cfg.Broker = GetBroker()
 	cfg.API = GetAPIConnString()
 	cfg.CoreDNSAddr = GetCoreDNSAddr()
 	cfg.APIPort = GetAPIPort()
@@ -105,7 +107,6 @@ func GetServerInfo() models.ServerConfig {
 		cfg.DNSMode = "on"
 	}
 	cfg.Version = GetVersion()
-	cfg.Server = GetServer()
 	cfg.Is_EE = Is_EE
 	cfg.StunPort = GetStunPort()
 
@@ -385,6 +386,17 @@ func GetServer() string {
 	return server
 }
 
+// GetBroker - gets the broker name
+func GetBroker() string {
+	server := ""
+	if os.Getenv("BROKER_NAME") != "" {
+		server = os.Getenv("BROKER_NAME")
+	} else if config.Config.Server.Broker != "" {
+		server = config.Config.Server.Broker
+	}
+	return server
+}
+
 func GetVerbosity() int32 {
 	var verbosity = 0
 	var err error