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/cmd/app_manager.go b/core/installer/cmd/app_manager.go
index a6224a0..16ac83a 100644
--- a/core/installer/cmd/app_manager.go
+++ b/core/installer/cmd/app_manager.go
@@ -16,11 +16,13 @@
 )
 
 var appManagerFlags struct {
-	sshKey           string
-	repoAddr         string
-	port             int
-	appRepoAddr      string
-	headscaleAPIAddr string
+	sshKey                 string
+	repoAddr               string
+	port                   int
+	appRepoAddr            string
+	headscaleAPIAddr       string
+	dnsAPIAddr             string
+	clusterProxyConfigPath string
 }
 
 func appManagerCmd() *cobra.Command {
@@ -58,6 +60,18 @@
 		"",
 		"",
 	)
+	cmd.Flags().StringVar(
+		&appManagerFlags.dnsAPIAddr,
+		"dns-api-addr",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&appManagerFlags.clusterProxyConfigPath,
+		"cluster-proxy-config-path",
+		"",
+		"",
+	)
 	return cmd
 }
 
@@ -92,8 +106,15 @@
 		return err
 	}
 	hf := installer.NewGitHelmFetcher()
-	vpnKeyGen := installer.NewHeadscaleAPIClient(appManagerFlags.headscaleAPIAddr)
-	m, err := installer.NewAppManager(repoIO, nsc, jc, hf, vpnKeyGen, "/apps")
+	vpnAPIClient := installer.NewHeadscaleAPIClient(appManagerFlags.headscaleAPIAddr)
+	cnc := &installer.NginxProxyConfigurator{
+		// TODO(gio): read from env config
+		PrivateSubdomain: "p",
+		DNSAPIAddr:       appManagerFlags.dnsAPIAddr,
+		Repo:             repoIO,
+		NginxConfigPath:  appManagerFlags.clusterProxyConfigPath,
+	}
+	m, err := installer.NewAppManager(repoIO, nsc, jc, hf, vpnAPIClient, cnc, "/apps")
 	if err != nil {
 		return err
 	}
@@ -117,16 +138,21 @@
 	} else {
 		r = installer.NewInMemoryAppRepository(installer.CreateStoreApps())
 	}
+	fr := installer.NewInMemoryAppRepository(installer.CreateAllEnvApps())
 	helmMon, err := newHelmReleaseMonitor()
 	if err != nil {
 		return err
 	}
 	s, err := welcome.NewAppManagerServer(
 		appManagerFlags.port,
+		repoIO,
 		m,
 		r,
+		fr,
 		tasks.NewFixedReconciler(env.Id, env.Id),
 		helmMon,
+		cnc,
+		vpnAPIClient,
 	)
 	if err != nil {
 		return err
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index 2fe1697..8512691 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -201,6 +201,8 @@
 		},
 	}
 	vpnKeyGen := installer.NewHeadscaleAPIClient(dodoAppFlags.headscaleAPIAddr)
+	// TOOD(gio): implement
+	var cnc installer.ClusterNetworkConfigurator
 	s, err := welcome.NewDodoAppServer(
 		st,
 		nf,
@@ -217,6 +219,7 @@
 		nsc,
 		jc,
 		vpnKeyGen,
+		cnc,
 		env,
 		dodoAppFlags.external,
 		dodoAppFlags.fetchUsersAddr,
diff --git a/core/installer/cmd/kube.go b/core/installer/cmd/kube.go
index f31ad8f..1a74731 100644
--- a/core/installer/cmd/kube.go
+++ b/core/installer/cmd/kube.go
@@ -2,10 +2,13 @@
 
 import (
 	"github.com/giolekva/pcloud/core/installer"
+	"github.com/giolekva/pcloud/core/installer/kube"
 )
 
 func newNSCreator() (installer.NamespaceCreator, error) {
-	return installer.NewNamespaceCreator(rootFlags.kubeConfig)
+	return installer.NewNamespaceCreator(kube.KubeConfigOpts{
+		KubeConfigPath: rootFlags.kubeConfig,
+	})
 }
 
 func newZoneFetcher() (installer.ZoneStatusFetcher, error) {
@@ -17,7 +20,9 @@
 }
 
 func newJobCreator() (installer.JobCreator, error) {
-	clientset, err := installer.NewKubeConfig(rootFlags.kubeConfig)
+	clientset, err := kube.NewKubeClient(kube.KubeConfigOpts{
+		KubeConfigPath: rootFlags.kubeConfig,
+	})
 	if err != nil {
 		return nil, err
 	}
diff --git a/core/installer/cmd/launcher.go b/core/installer/cmd/launcher.go
index e671fe6..85e811f 100644
--- a/core/installer/cmd/launcher.go
+++ b/core/installer/cmd/launcher.go
@@ -74,7 +74,7 @@
 	if err != nil {
 		return err
 	}
-	appManager, err := installer.NewAppManager(repoIO, nil, nil, nil, nil, "/apps")
+	appManager, err := installer.NewAppManager(repoIO, nil, nil, nil, nil, nil, "/apps")
 	if err != nil {
 		return err
 	}
diff --git a/core/installer/cmd/rewrite.go b/core/installer/cmd/rewrite.go
index 3e2961b..0f018e4 100644
--- a/core/installer/cmd/rewrite.go
+++ b/core/installer/cmd/rewrite.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"fmt"
 	"log"
 	"os"
 
@@ -62,16 +61,7 @@
 	log.Println("Creating repository")
 	r := installer.NewInMemoryAppRepository(installer.CreateAllApps())
 	hf := installer.NewGitHelmFetcher()
-	mgr, err := installer.NewAppManager(repoIO, nil, nil, hf, nil, "/apps")
-	if err != nil {
-		return err
-	}
-	env, err := mgr.Config()
-	if err != nil {
-		return err
-	}
-	fmt.Printf("%+v\n", env)
-	log.Println("Read config")
+	mgr, err := installer.NewAppManager(repoIO, nil, nil, hf, nil, nil, "/apps")
 	if err != nil {
 		return err
 	}