Installer: Add option to expose PostgreSQL

Change-Id: I1e00bc4d1d1f417956c1744634287c32d9e3f513
diff --git a/core/installer/app_configs/app_base.cue b/core/installer/app_configs/app_base.cue
index 4f59a73..3ed300a 100644
--- a/core/installer/app_configs/app_base.cue
+++ b/core/installer/app_configs/app_base.cue
@@ -483,7 +483,7 @@
 		}
 	}
 	helm: {
-		postgres: {
+		"postgres-\(name)": {
 			chart: charts.postgres
 			if _cluster != _|_ {
 				cluster: _cluster
@@ -602,7 +602,17 @@
 }
 
 output: {
-	openPort: list.Concat([for out in outs { out.openPort }])
+	_op: {
+		for i, out in outs {
+			for x, opm in out.openPortMap {
+				for k, v in opm {
+					"\(i)-\(x)-\(k)": v
+				}
+			}
+		}
+	}
+	_op1: [for i in _op { i }]
+	openPort: list.FlattenN(_op1, 1)
 	for _, out in outs {
 		images: out.images
 		charts: out.charts
@@ -660,6 +670,11 @@
 	images: {...}
 	charts: {...}
 	helm: {...}
+	openPort: [...#PortForward]
+	openPortMap: {
+		"_self": openPort
+		...
+	}
 	clusterProxy: {...}
 	openPort: [...#PortForward] | *[]
 	images: {
@@ -794,6 +809,12 @@
 		}
 		...
 	}
+	openPortMap: {
+		for k, v in postgresql {
+			"postgresql-\(k)": v.openPort
+		}
+		...
+	}
 	images: {
 		for k, v in postgresql {
 			for x, y in v.images {
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index 76b0220..7d49d37 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -18,6 +18,46 @@
 
 _devVM: {}
 
+#PSQL: {
+	name: string
+	size: string | *"1Gi"
+	cluster?: string
+	expose: [...string] | *[]
+}
+
+postgresql: [...#PSQL] | *[]
+_postgresql: postgresql
+
+input: {
+	for psql in _postgresql {
+		for i, e in psql.expose {
+			"port_\(psql.name)_\(i)": int @role(port)
+		}
+	}
+}
+
+out: {
+	postgresql: {
+		for psql in _postgresql {
+			"\(psql.name)": #PostgreSQL & {
+				name: psql.name
+				size: psql.size
+				if psql.cluster != _|_ {
+					cluster: clusterMap[strings.ToLower(app.cluster)]
+				}
+				openPort: [for i, e in psql.expose {
+					network: networks[strings.ToLower(e)]
+					port: input["port_\(psql.name)_\(i)"]
+					protocol: "TCP"
+					service: {
+						name: "postgres-\(psql.name)"
+						port: 5432
+					}
+				}]
+			}
+		}
+	}
+}
 if app.dev.enabled {
 	input: {
 		username?: string | *app.dev.username
@@ -484,6 +524,7 @@
 			"\(v.name)": v
 		}
 	}
+	// TODO(gio): remove
 	postgresql: {
 		for v in app.postgresql {
 			"\(v.name)": v
diff --git a/core/installer/dodo_app_test.go b/core/installer/dodo_app_test.go
new file mode 100644
index 0000000..e9d7737
--- /dev/null
+++ b/core/installer/dodo_app_test.go
@@ -0,0 +1,69 @@
+package installer
+
+import (
+	"testing"
+
+	"cuelang.org/go/cue/errors"
+)
+
+var exposedPostgreSQL = `
+app: {
+	type: "golang:1.20.0"
+	run: "main.go"
+	ingress: {
+		network: "private"
+		subdomain: "testapp"
+		auth: enabled: false
+	}
+}
+
+postgresql: [{
+	name: "db_1"
+	expose: ["private", "public", "private"]
+}, {
+	name: "db_2"
+	expose: ["public", "private", "public"]
+}]`
+
+func TestExposedPostgreSQL(t *testing.T) {
+	app, err := NewDodoApp([]byte(exposedPostgreSQL))
+	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, nil, map[string]any{
+		"repoAddr":       "",
+		"repoPublicAddr": "",
+		"managerAddr":    "",
+		"appId":          "",
+		"branch":         "",
+		"sshPrivateKey":  "",
+		"port_db_1_0":    1,
+		"port_db_1_1":    2,
+		"port_db_1_2":    3,
+		"port_db_2_0":    4,
+		"port_db_2_1":    5,
+		"port_db_2_2":    6,
+	}, nil, keyGen)
+	if err != nil {
+		for _, e := range errors.Errors(err) {
+			for _, f := range errors.Errors(e) {
+				for _, g := range errors.Errors(f) {
+					t.Log(g)
+				}
+			}
+		}
+		t.Fatal(err)
+	}
+	t.Log(string(r.Raw))
+}