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/app.go b/core/installer/app.go
index 91f290f..f023b18 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -33,10 +33,13 @@
 type rendered struct {
 	Name            string
 	Readme          string
+	Cluster         string
+	Namespaces      []Namespace
 	Resources       CueAppData
 	HelmCharts      HelmCharts
 	ContainerImages map[string]ContainerImage
 	Ports           []PortForward
+	ClusterProxies  map[string]ClusterProxy
 	Data            CueAppData
 	URL             string
 	Help            []HelpDocument
@@ -44,6 +47,11 @@
 	Raw             []byte
 }
 
+type Namespace struct {
+	Name       string `json:"name"`
+	Kubeconfig string `json:"kubeconfig,omitempty"`
+}
+
 type HelpDocument struct {
 	Title    string
 	Contents string
@@ -81,6 +89,11 @@
 	Config InfraAppInstanceConfig
 }
 
+type ClusterProxy struct {
+	From string `json:"from"`
+	To   string `json:"to"`
+}
+
 type PortForward struct {
 	Allocator     string `json:"allocator"`
 	ReserveAddr   string `json:"reservator"`
@@ -200,6 +213,7 @@
 		release Release,
 		env EnvConfig,
 		networks []Network,
+		clusters []Cluster,
 		values map[string]any,
 		charts map[string]helmv2.HelmChartTemplateSpec,
 		vpnKeyGen VPNAPIClient,
@@ -342,6 +356,13 @@
 		return rendered{}, err
 	}
 	ret.Readme = readme
+	res.LookupPath(cue.ParsePath("input.cluster.name")).Decode(&ret.Cluster)
+	if err := res.LookupPath(cue.ParsePath("output.clusterProxy")).Decode(&ret.ClusterProxies); err != nil {
+		return rendered{}, err
+	}
+	if err := res.LookupPath(cue.ParsePath("namespaces")).Decode(&ret.Namespaces); err != nil {
+		return rendered{}, err
+	}
 	if err := res.LookupPath(cue.ParsePath("portForward")).Decode(&ret.Ports); err != nil {
 		return rendered{}, err
 	}
@@ -457,14 +478,16 @@
 	release Release,
 	env EnvConfig,
 	networks []Network,
+	clusters []Cluster,
 	values map[string]any,
 	charts map[string]helmv2.HelmChartTemplateSpec,
 	vpnKeyGen VPNAPIClient,
 ) (EnvAppRendered, error) {
-	derived, err := deriveValues(values, values, a.Schema(), networks, vpnKeyGen)
+	derived, err := deriveValues(values, values, a.Schema(), networks, clusters, vpnKeyGen)
 	if err != nil {
 		return EnvAppRendered{}, err
 	}
+	// return EnvAppRendered{}, fmt.Errorf("asdasd")
 	if charts == nil {
 		charts = make(map[string]helmv2.HelmChartTemplateSpec)
 	}