ClusterManager: Implements support of remote clusters.

After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
  to another server from that same cluster

This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
  default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
  remote cluster service from ingress installed on default cluster.

Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
  old cluster keeps running app pods even though no ingress can
  reach it anymore.

Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/installer/vpn.go b/core/installer/vpn.go
index 01161df..beeb8e4 100644
--- a/core/installer/vpn.go
+++ b/core/installer/vpn.go
@@ -6,6 +6,7 @@
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"net/url"
 )
@@ -15,6 +16,7 @@
 	ExpireKey(username, key string) error
 	ExpireNode(username, node string) error
 	RemoveNode(username, node string) error
+	GetNodeIP(username, node string) (net.IP, error)
 }
 
 type headscaleAPIClient struct {
@@ -108,3 +110,34 @@
 	}
 	return nil
 }
+
+func (g *headscaleAPIClient) GetNodeIP(username, node string) (net.IP, error) {
+	addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s/ip", g.apiAddr, username, node))
+	if err != nil {
+		return nil, err
+	}
+	resp, err := g.c.Do(&http.Request{
+		URL:    addr,
+		Method: http.MethodGet,
+		Body:   nil,
+	})
+	if err != nil {
+		return nil, err
+	}
+	var buf bytes.Buffer
+	if _, err := io.Copy(&buf, resp.Body); err != nil {
+		return nil, err
+	}
+	bufS := buf.String()
+	if resp.StatusCode == http.StatusNotFound {
+		return nil, ErrorNotFound
+	}
+	if resp.StatusCode != http.StatusOK {
+		return nil, errors.New(bufS)
+	}
+	ip := net.ParseIP(bufS)
+	if ip == nil {
+		return nil, fmt.Errorf("invalid ip")
+	}
+	return ip, nil
+}