AppManager: Support exposing ports on public networks

Sets up [public ingress -> private ingress -> remote cluster] proxy chain

Change-Id: I6376a006b2692c31f3cb895bbfd6e373182e8824
diff --git a/core/installer/app.go b/core/installer/app.go
index e57fee8..9c658df 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -102,8 +102,9 @@
 	Protocol    string `json:"protocol"`
 	Port        int    `json:"port"`
 	Service     struct {
-		Name string `json:"name"`
-		Port int    `json:"port"`
+		Name      string `json:"name"`
+		Namespace string `json:"namespace,omitempty"`
+		Port      int    `json:"port"`
 	} `json:"service"`
 }
 
diff --git a/core/installer/app_configs/app_base.cue b/core/installer/app_configs/app_base.cue
index 7330b1e..8a674ee 100644
--- a/core/installer/app_configs/app_base.cue
+++ b/core/installer/app_configs/app_base.cue
@@ -115,6 +115,7 @@
 	port: int
 	service: close({
 		name: string
+		namespace: string | *""
 		port: #PortValue
 	})
 	protocol: "TCP" | "UDP" | *"TCP"
@@ -518,31 +519,69 @@
 			}
 		}
 		if 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["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).private-network-proxy.devices.\(global.privateDomain)"
+			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).private-network-proxy.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).private-network-proxy.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)
 
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index 6f7001a..963421e 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -26,6 +26,9 @@
 			"port_postgresql_\(v.name)_\(i)": int @role(port)
 			if input.cluster != _|_ {
 				"port_postgresql_\(v.name)_\(i)_cluster": int @role(port)
+				if strings.ToLower(e.network) == "public" {
+					"port_postgresql_\(v.name)_\(i)_private": int @role(port)
+				}
 			}
 		}
 	}
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 3285c19..0ec6605 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -216,7 +216,11 @@
 	for _, p := range ports {
 		var target string
 		if p.Cluster == "" {
-			target = fmt.Sprintf("%s/%s", ns, p.Service.Name)
+			if p.Service.Namespace == "" {
+				target = fmt.Sprintf("%s/%s", ns, p.Service.Name)
+			} else {
+				target = fmt.Sprintf("%s/%s", p.Service.Namespace, p.Service.Name)
+			}
 		} else {
 			target = p.Service.Name
 		}
@@ -258,10 +262,16 @@
 	var retErr error
 	for _, p := range ports {
 		var buf bytes.Buffer
+		var fullName string
+		if p.Service.Namespace == "" {
+			fullName = fmt.Sprintf("%s/%s", ns, p.Service.Name)
+		} else {
+			fullName = fmt.Sprintf("%s/%s", p.Service.Namespace, p.Service.Name)
+		}
 		req := removePortReq{
 			Protocol:      p.Protocol,
 			SourcePort:    p.Port,
-			TargetService: fmt.Sprintf("%s/%s", ns, p.Service.Name),
+			TargetService: fullName,
 			TargetPort:    p.Service.Port,
 		}
 		if err := json.NewEncoder(&buf).Encode(req); err != nil {
diff --git a/core/installer/canvas-app.cue b/core/installer/canvas-app.cue
index 6843277..89546c0 100644
--- a/core/installer/canvas-app.cue
+++ b/core/installer/canvas-app.cue
@@ -28,8 +28,8 @@
 		  "name": "pgg",
 		  "size": "2Gi",
 		  "expose": [{
-		    "network": "private",
-			"subdomain": "pggp"
+		    "network": "public",
+			"subdomain": "piopio"
 		  }]
 	  }],
 	  "cluster": "asdc"
diff --git a/core/installer/dodo_app_test.go b/core/installer/dodo_app_test.go
index eaad590..6c4df1f 100644
--- a/core/installer/dodo_app_test.go
+++ b/core/installer/dodo_app_test.go
@@ -213,7 +213,7 @@
 	t.Log(string(r.Raw))
 }
 
-const exposeRemoteCluster = `
+const exposeRemoteClusterPrivateNetwork = `
 {
     "cluster": "remote",
     "postgresql": [{
@@ -227,9 +227,9 @@
 }
 `
 
-func TestExposeRemoteCluster(t *testing.T) {
+func TestExposeRemoteClusterPrivateNetwork(t *testing.T) {
 	var buf bytes.Buffer
-	if _, err := buf.WriteString(exposeRemoteCluster); err != nil {
+	if _, err := buf.WriteString(exposeRemoteClusterPrivateNetwork); err != nil {
 		t.Fatal(err)
 	}
 	clusters := []Cluster{{
@@ -270,3 +270,62 @@
 	}
 	t.Log(string(r.Raw))
 }
+
+const exposeRemoteClusterPublicNetwork = `
+{
+    "cluster": "remote",
+    "postgresql": [{
+		"name": "db",
+		"size": "1Gi",
+		"expose": [{
+			"network": "Public",
+			"subdomain": "pg"
+		}]
+	}],
+}
+`
+
+func TestExposeRemoteClusterPublicNetwork(t *testing.T) {
+	var buf bytes.Buffer
+	if _, err := buf.WriteString(exposeRemoteClusterPublicNetwork); err != nil {
+		t.Fatal(err)
+	}
+	clusters := []Cluster{{
+		Name:             "remote",
+		Kubeconfig:       "<KUBECONFIG>",
+		IngressClassName: "<INGRESS_CLASS_NAME>",
+	}}
+	if err := json.NewEncoder(&buf).Encode(struct {
+		Clusters []Cluster `json:"clusters"`
+	}{
+		clusters,
+	}); err != nil {
+		t.Fatal(err)
+	}
+	app, err := NewDodoApp(buf.Bytes())
+	if err != nil {
+		for _, e := range errors.Errors(err) {
+			t.Log(e)
+		}
+		t.Fatal(err)
+	}
+	release := Release{
+		Namespace:     "foo",
+		AppInstanceId: "foo-bar",
+		RepoAddr:      "ssh://192.168.100.210:22/config",
+		AppDir:        "/foo/bar",
+	}
+	keyGen := testKeyGen{}
+	r, err := app.Render(release, env, networks, clusters, map[string]any{
+		"managerAddr":                  "",
+		"appId":                        "",
+		"sshPrivateKey":                "",
+		"port_postgresql_db_0":         1,
+		"port_postgresql_db_0_cluster": 2,
+		"port_postgresql_db_0_private": 3,
+	}, nil, keyGen)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(string(r.Raw))
+}
diff --git a/core/port-allocator/main.go b/core/port-allocator/main.go
index 8b3ab80..7795993 100644
--- a/core/port-allocator/main.go
+++ b/core/port-allocator/main.go
@@ -223,7 +223,6 @@
 			c.preOpenPorts = append(c.preOpenPorts, p)
 			c.blocklist[p] = struct{}{}
 		}
-		return nil
 	}
 	if c.proxyCfg != nil && len(c.proxyPreOpenPorts) < c.minPreOpenPorts {
 		for count := c.preOpenPortsBatchSize; count > 0; count-- {
@@ -238,7 +237,6 @@
 			c.proxyPreOpenPorts = append(c.proxyPreOpenPorts, p)
 			c.blocklist[p] = struct{}{}
 		}
-		return nil
 	}
 	if len(ports) == 0 {
 		return nil
@@ -271,15 +269,11 @@
 			if err != nil {
 				return "", err
 			}
-			fmt.Printf("%+v\n", tcp)
-			fmt.Printf("%+v\n", udp)
 			for _, p := range ports {
 				ps := strconv.Itoa(p)
 				tcp[ps] = p
 				udp[ps] = p
 			}
-			fmt.Printf("%+v\n", tcp)
-			fmt.Printf("%+v\n", udp)
 			if err := c.writeRelease(fs, rel); err != nil {
 				return "", err
 			}
@@ -654,7 +648,6 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	fmt.Printf("%+v\n", req)
 	var port int
 	var secret string
 	var err error