AppManager: Reuse cross-cluster port forwarding logic

Services define single open port with optional cluster and
outer layer converts it to cross cluster bindings.

Change-Id: I2f83270d19aaa367789d19a3ffbdf3a2158c1cf8
diff --git a/core/installer/app.go b/core/installer/app.go
index 9c658df..538e2ff 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -95,13 +95,11 @@
 }
 
 type PortForward struct {
-	Cluster     string `json:"cluster,omitempty"`
-	Allocator   string `json:"allocator"`
-	ReserveAddr string `json:"reservator"`
-	RemoveAddr  string `json:"deallocator"`
-	Protocol    string `json:"protocol"`
-	Port        int    `json:"port"`
-	Service     struct {
+	Cluster  string  `json:"clusterName,omitempty"`
+	Network  Network `json:"network"`
+	Protocol string  `json:"protocol"`
+	Port     int     `json:"port"`
+	Service  struct {
 		Name      string `json:"name"`
 		Namespace string `json:"namespace,omitempty"`
 		Port      int    `json:"port"`
diff --git a/core/installer/app_configs/app_base.cue b/core/installer/app_configs/app_base.cue
index 1d81e62..e64d6d5 100644
--- a/core/installer/app_configs/app_base.cue
+++ b/core/installer/app_configs/app_base.cue
@@ -110,8 +110,9 @@
 }
 
 #PortForward: {
+	name: string
 	network: #Network
-	cluster?: string | null
+	clusterName?: string | null
 	port: int
 	service: close({
 		name: string
@@ -360,82 +361,16 @@
 	_volumeClaimName: "\(name)-mongodb"
 	_initdbScripts: initdbScripts
 
-	openPort: list.FlattenN([for i, e in expose {
-		if cluster == _|_ {
-			network: networks[strings.ToLower(e.network)]
-			port: input["port_mongodb_\(_name)_\(i)"]
-			protocol: "TCP"
-			service: {
-				name: "mongodb-\(_name)"
-				port: 27017
-			}
+	openPort: [for i, e in expose {
+		name: "port_mongodb_\(_name)_\(i)"
+		network: networks[strings.ToLower(e.network)]
+		port: input[name]
+		protocol: "TCP"
+		service: {
+			name: "mongodb-\(_name)"
+			port: 27017
 		}
-		if cluster != _|_ {
-			if strings.ToLower(e.network) == "private" {
-				[{
-					network: #Network & {
-						name: "cluster_\(cluster.name)"
-						ingressClass: "default"
-						domain: ""
-						allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
-						reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
-						deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
-					}
-					port: input["port_mongodb_\(_name)_\(i)_cluster"]
-					protocol: "TCP"
-					service: {
-						name: "mongodb-\(_name)"
-						port: 27017
-					}
-				}, {
-					cluster: _cluster.name
-					network: networks[strings.ToLower(e.network)]
-					port: input["port_mongodb_\(_name)_\(i)"]
-					protocol: "TCP"
-					service: {
-						name: "cluster-\(cluster).devices.\(global.privateDomain)"
-						port: input["port_mongodb_\(_name)_\(i)_cluster"]
-					}
-				}]
-			}
-			if strings.ToLower(e.network) != "private" {
-				[{
-					network: #Network & {
-						name: "cluster_\(cluster.name)"
-						ingressClass: "default"
-						domain: ""
-						allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
-						reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
-						deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
-					}
-					port: input["port_mongodb_\(_name)_\(i)_cluster"]
-					protocol: "TCP"
-					service: {
-						name: "mongodb-\(_name)"
-						port: 27017
-					}
-				}, {
-					network: networks.private // TODO(gio): take corresponding private network
-					cluster: _cluster.name
-					port: input["port_mongodb_\(_name)_\(i)_private"]
-					protocol: "TCP"
-					service: {
-						name: "cluster-\(cluster).devices.\(global.privateDomain)"
-						port: input["port_mongodb_\(_name)_\(i)_cluster"]
-					}
-				}, {
-					network: networks[strings.ToLower(e.network)]
-					port: input["port_mongodb_\(_name)_\(i)"]
-					protocol: "TCP"
-					service: {
-						name: "\(global.id)-nginx-private-controller"
-						namespace: "\(global.namespacePrefix)ingress-private"
-						port: input["port_mongodb_\(_name)_\(i)_private"]
-					}
-				}]
-			}
-		}
-	}], -1)
+	}]
 
 	images: {
 		mongodb: {
@@ -530,7 +465,7 @@
 	}
  	openPortMap: {
 		for k, v in mongodb {
-			"mongodb-\(k)": v.openPort
+			"mongodb-\(k)": v.openPortMap
 		}
 		...
 	}
@@ -582,82 +517,16 @@
 	_volumeClaimName: "\(name)-postgresql"
 	_name: name
 
-	openPort: list.FlattenN([for i, e in expose {
-		if cluster == _|_ {
-			network: networks[strings.ToLower(e.network)]
-			port: input["port_postgresql_\(_name)_\(i)"]
-			protocol: "TCP"
-			service: {
-				name: "postgres-\(_name)"
-				port: 5432
-			}
+	openPort: [for i, e in expose {
+		name: "port_postgresql_\(_name)_\(i)"
+		network: networks[strings.ToLower(e.network)]
+		port: input[name]
+		protocol: "TCP"
+		service: {
+			name: "postgres-\(_name)"
+			port: 5432
 		}
-		if cluster != _|_ {
-			if strings.ToLower(e.network) == "private" {
-				[{
-					network: #Network & {
-						name: "cluster_\(cluster.name)"
-						ingressClass: "default"
-						domain: ""
-						allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
-						reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
-						deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
-					}
-					port: input["port_postgresql_\(_name)_\(i)_cluster"]
-					protocol: "TCP"
-					service: {
-						name: "postgres-\(_name)"
-						port: 5432
-					}
-				}, {
-					cluster: _cluster.name
-					network: networks[strings.ToLower(e.network)]
-					port: input["port_postgresql_\(_name)_\(i)"]
-					protocol: "TCP"
-					service: {
-						name: "cluster-\(cluster).devices.\(global.privateDomain)"
-						port: input["port_postgresql_\(_name)_\(i)_cluster"]
-					}
-				}]
-			}
-			if strings.ToLower(e.network) != "private" {
-				[{
-					network: #Network & {
-						name: "cluster_\(cluster.name)"
-						ingressClass: "default"
-						domain: ""
-						allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
-						reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
-						deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
-					}
-					port: input["port_postgresql_\(_name)_\(i)_cluster"]
-					protocol: "TCP"
-					service: {
-						name: "postgres-\(_name)"
-						port: 5432
-					}
-				}, {
-					network: networks.private // TODO(gio): take corresponding private network
-					cluster: _cluster.name
-					port: input["port_postgresql_\(_name)_\(i)_private"]
-					protocol: "TCP"
-					service: {
-						name: "cluster-\(cluster).devices.\(global.privateDomain)"
-						port: input["port_postgresql_\(_name)_\(i)_cluster"]
-					}
-				}, {
-					network: networks[strings.ToLower(e.network)]
-					port: input["port_postgresql_\(_name)_\(i)"]
-					protocol: "TCP"
-					service: {
-						name: "\(global.id)-nginx-private-controller"
-						namespace: "\(global.namespacePrefix)ingress-private"
-						port: input["port_postgresql_\(_name)_\(i)_private"]
-					}
-				}]
-			}
-		}
-	}], -1)
+	}]
 
 	images: {
 		postgres: {
@@ -812,8 +681,8 @@
 			}
 		}
 	}
-	_op1: [for i in _op { i }]
-	openPort: list.FlattenN(_op1, 1)
+	openPort: [...#PortForward] | *[]
+	openPort: list.FlattenN([for i in _op { i }], -1)
 	for _, out in outs {
 		images: out.images
 		charts: out.charts
@@ -873,7 +742,75 @@
 	helm: {...}
 	openPort: [...#PortForward] | *[]
 	openPortMap: {
-		"_self": openPort
+		"_self": list.FlattenN([for i, e in openPort {
+			if cluster == _|_ {
+				e
+			}
+			if cluster != _|_ {
+				if strings.ToLower(e.network.name) == "private" {
+					[{
+						name: "\(e.name)_cluster"
+						network: #Network & {
+							name: "cluster_\(cluster.name)"
+							ingressClass: "default"
+							domain: ""
+							allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
+							reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
+							deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
+						}
+						port: input[name]
+						protocol: e.protocol
+						service: e.service
+					}, {
+						name: e.name
+						clusterName: _cluster.name
+						network: e.network
+						port: input[e.name]
+						protocol: e.protocol
+						service: {
+							name: "cluster-\(clusterName).devices.\(global.privateDomain)"
+							port: input["\(e.name)_cluster"]
+						}
+					}]
+				}
+				if strings.ToLower(e.network.name) != "private" {
+					[{
+						name: "\(e.name)_cluster"
+						network: #Network & {
+							name: "cluster_\(cluster.name)"
+							ingressClass: "default"
+							domain: ""
+							allocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/allocate"
+							reservePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/reserve"
+							deallocatePortAddr: "http://port-allocator.\(global.id)-cluster-\(cluster.name)-network.svc.cluster.local/api/remmove"
+						}
+						port: input[name]
+						protocol: "TCP"
+						service: e.service
+					}, {
+						name: "\(e.name)_private"
+						network: networks.private // TODO(gio): take corresponding private network
+						clusterName: _cluster.name
+						port: input[name]
+						protocol: "TCP"
+						service: {
+							name: "cluster-\(clusterName).devices.\(global.privateDomain)"
+							port: input["\(e.name)_cluster"]
+						}
+					}, {
+						name: e.name
+						network: e.network
+						port: input[name]
+						protocol: "TCP"
+						service: {
+							name: "\(global.id)-nginx-private-controller"
+							namespace: "\(global.namespacePrefix)ingress-private"
+							port: input["\(e.name)_private"]
+						}
+					}]
+				}
+			}
+		}], -1)
 		...
 	}
 	clusterProxy: {...}
@@ -1011,7 +948,7 @@
 	}
 	openPortMap: {
 		for k, v in postgresql {
-			"postgresql-\(k)": v.openPort
+			"postgresql-\(k)": v.openPortMap
 		}
 		...
 	}
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index 608d629..a22efe6 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -391,8 +391,9 @@
 						openPort: list.Concat([
 							[for i, e in svc.expose if e.port.name != _|_ {
 								for p in svc.ports if e.port.name == p.name {
+									name: "port_service_app_\(i)"
 									network: networks[strings.ToLower(e.network)]
-									port: input["port_service_app_\(i)"] // TODO(gio): app name
+									port: input[name] // TODO(gio): app name
 									protocol: "TCP"
 									service: {
 										name: "app-app"
@@ -402,8 +403,9 @@
 							}],
 							[for i, e in svc.expose if e.port.value != _|_ {
 								for p in svc.ports if e.port.value == p.value {
+									name: "port_service_app_\(i)"
 									network: networks[strings.ToLower(e.network)]
-									port: input["port_service_app_\(i)"] // TODO(gio): app name
+									port: input[name] // TODO(gio): app name
 									protocol: "TCP"
 									service: {
 										name: "app-app"
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 0ec6605..d95e640 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -278,7 +278,7 @@
 			retErr = err
 			continue
 		}
-		resp, err := http.Post(p.RemoveAddr, "application/json", &buf)
+		resp, err := http.Post(p.Network.DeallocatePortAddr, "application/json", &buf)
 		if err != nil {
 			retErr = err
 			continue
@@ -504,10 +504,10 @@
 	allocators := map[string]string{}
 	for _, pf := range rendered.Ports {
 		reservators[portFields[pf.Port]] = reservePortInfo{
-			reserveAddr: pf.ReserveAddr,
+			reserveAddr: pf.Network.ReservePortAddr,
 			RemoteProxy: pf.Cluster != "",
 		}
-		allocators[portFields[pf.Port]] = pf.Allocator
+		allocators[portFields[pf.Port]] = pf.Network.AllocatePortAddr
 	}
 	portReservations, err := reservePorts(reservators)
 	if err != nil {
diff --git a/core/installer/dodo_app_test.go b/core/installer/dodo_app_test.go
index 6c4df1f..25ce4a3 100644
--- a/core/installer/dodo_app_test.go
+++ b/core/installer/dodo_app_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"testing"
 
 	"cuelang.org/go/cue/errors"
@@ -269,6 +270,7 @@
 		t.Fatal(err)
 	}
 	t.Log(string(r.Raw))
+	t.Log(fmt.Sprintf("%+v", r.Ports))
 }
 
 const exposeRemoteClusterPublicNetwork = `
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index 5ad606e..5b0f865 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -113,6 +113,7 @@
 	}
 
 	openPort: [{
+		name: "ssh"
 		network: input.network
 		port: input.sshPort
 		service: {
diff --git a/core/installer/values-tmpl/gerrit.cue b/core/installer/values-tmpl/gerrit.cue
index c783817..9742cdb 100644
--- a/core/installer/values-tmpl/gerrit.cue
+++ b/core/installer/values-tmpl/gerrit.cue
@@ -46,6 +46,7 @@
 	}
 
 	openPort: [{
+		name: "ssh"
 		network: input.network
 		port: input.sshPort
 		service: {
diff --git a/core/installer/values-tmpl/soft-serve.cue b/core/installer/values-tmpl/soft-serve.cue
index 23e07bb..dead3db 100644
--- a/core/installer/values-tmpl/soft-serve.cue
+++ b/core/installer/values-tmpl/soft-serve.cue
@@ -69,6 +69,7 @@
 	}
 
 	openPort: [{
+		name: "ssh"
 		network: input.network
 		port: input.sshPort
 		service: {