DodoApp: Output access points

Change-Id: I078adfd43bd254e260bf63113a2fb3ab059c7706
diff --git a/core/installer/app.go b/core/installer/app.go
index 0b59ca1..a971653 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -31,6 +31,109 @@
 //go:embed app_configs/app_global_infra.cue
 var cueInfraAppGlobal []byte
 
+type Access struct {
+	Type       string `json:"type"`
+	Name       string `json:"name"`
+	HTTPS      *AccessHTTPS
+	SSH        *AccessSSH
+	TCP        *AccessTCP
+	UDP        *AccessUDP
+	PostgreSQL *AccessPostgreSQL
+	MongoDB    *AccessMongoDB
+}
+
+func (a Access) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+	switch a.Type {
+	case "https":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessHTTPS
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.HTTPS, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	case "ssh":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessSSH
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.SSH, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	case "tcp":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessTCP
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.TCP, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	case "udp":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessUDP
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.UDP, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	case "postgresql":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessPostgreSQL
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.PostgreSQL, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	case "mongodb":
+		if err := json.NewEncoder(&buf).Encode(struct {
+			AccessMongoDB
+			Type string `json:"type"`
+			Name string `json:"name"`
+		}{*a.MongoDB, a.Type, a.Name}); err != nil {
+			return nil, err
+		}
+	default:
+		panic("MUST NOT REACH!")
+	}
+	return buf.Bytes(), nil
+}
+
+type AccessHTTPS struct {
+	Address string `json:"address"`
+}
+
+type AccessSSH struct {
+	Host string `json:"host"`
+	Port int    `json:"port"`
+}
+
+type AccessTCP struct {
+	Host string `json:"host"`
+	Port int    `json:"port"`
+}
+
+type AccessUDP struct {
+	Host string `json:"host"`
+	Port int    `json:"port"`
+}
+
+type AccessPostgreSQL struct {
+	Host     string `json:"host"`
+	Port     int    `json:"port"`
+	Database string `json:"database"`
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
+type AccessMongoDB struct {
+	Host     string `json:"host"`
+	Port     int    `json:"port"`
+	Database string `json:"database"`
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
 type rendered struct {
 	Name            string
 	Readme          string
@@ -45,6 +148,7 @@
 	URL             string
 	Help            []HelpDocument
 	Icon            string
+	Access          []Access
 	Raw             []byte
 }
 
@@ -449,6 +553,122 @@
 		return rendered{}, err
 	}
 	ret.Icon = icon
+	access, err := extractAccess(res.LookupPath(cue.ParsePath("outs")))
+	if err != nil {
+		return rendered{}, err
+	}
+	ret.Access = access
+	return ret, nil
+}
+
+func extractAccessInternal(v cue.Value) ([]Access, error) {
+	ret := []Access{}
+	a := v.LookupPath(cue.ParsePath("access"))
+	if err := a.Err(); err != nil {
+		return nil, err
+	}
+	i, err := a.List()
+	if err != nil {
+		return nil, err
+	}
+	for i.Next() {
+		n := i.Value().LookupPath(cue.ParsePath("name"))
+		if err := n.Err(); err != nil {
+			return nil, err
+		}
+		nn, err := n.String()
+		if err != nil {
+			return nil, err
+		}
+		t := i.Value().LookupPath(cue.ParsePath("type"))
+		if err := t.Err(); err != nil {
+			return nil, err
+		}
+		d, err := t.String()
+		if err != nil {
+			return nil, err
+		}
+		switch d {
+		case "https":
+			{
+				var q AccessHTTPS
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "https", Name: nn, HTTPS: &q})
+			}
+		case "ssh":
+			{
+				var q AccessSSH
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "ssh", Name: nn, SSH: &q})
+			}
+		case "tcp":
+			{
+				var q AccessTCP
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "tcp", Name: nn, TCP: &q})
+			}
+		case "udp":
+			{
+				var q AccessUDP
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "udp", Name: nn, UDP: &q})
+			}
+		case "postgresql":
+			{
+				var q AccessPostgreSQL
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "postgresql", Name: nn, PostgreSQL: &q})
+			}
+		case "mongodb":
+			{
+				var q AccessMongoDB
+				if err := i.Value().Decode(&q); err != nil {
+					return nil, err
+				}
+				ret = append(ret, Access{Type: "mongodb", Name: nn, MongoDB: &q})
+			}
+		}
+	}
+	for _, sub := range []string{"ingress", "postgresql", "mongodb", "services", "vm"} {
+		subout := v.LookupPath(cue.ParsePath(sub))
+		if subout.Err() != nil {
+			continue
+		}
+		if a, err := extractAccess(subout); err != nil {
+			return nil, err
+		} else {
+			ret = append(ret, a...)
+		}
+	}
+	return ret, nil
+}
+
+func extractAccess(v cue.Value) ([]Access, error) {
+	if err := v.Err(); err != nil {
+		return nil, err
+	}
+	i, err := v.Fields()
+	if err != nil {
+		return nil, err
+	}
+	ret := []Access{}
+	for i.Next() {
+		if a, err := extractAccessInternal(i.Value()); err != nil {
+			return nil, fmt.Errorf(errors.Details(err, nil))
+		} else {
+			ret = append(ret, a...)
+		}
+	}
 	return ret, nil
 }