AppManager: Add API endpoint to install dodo app

Refactors cue definitions.

Next steps:
* Needs some cleanup, namespace is hard coded ...
* Maybe merge with regular install API
* Support exposing ports across clusters

Change-Id: Ibfc3c3f742b61f2c5874012fe6c77b958eae81d9
diff --git a/core/installer/server/appmanager/server.go b/core/installer/server/appmanager/server.go
index 5e1e06b..b3a0883 100644
--- a/core/installer/server/appmanager/server.go
+++ b/core/installer/server/appmanager/server.go
@@ -1,6 +1,7 @@
 package appmanager
 
 import (
+	"bytes"
 	"context"
 	"embed"
 	"encoding/json"
@@ -9,6 +10,7 @@
 	"html/template"
 	"net"
 	"net/http"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -138,6 +140,7 @@
 	r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
 	r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
 	r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
+	r.HandleFunc("/api/dodo-app", s.handleDodoAppInstall).Methods(http.MethodPost)
 	r.HandleFunc("/clusters/{cluster}/servers/{server}/remove", s.handleClusterRemoveServer).Methods(http.MethodPost)
 	r.HandleFunc("/clusters/{cluster}/servers", s.handleClusterAddServer).Methods(http.MethodPost)
 	r.HandleFunc("/clusters/{name}", s.handleCluster).Methods(http.MethodGet)
@@ -154,6 +157,47 @@
 	return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
 }
 
+type dodoAppInstallReq struct {
+	Id            string         `json:"id"`
+	SSHPrivateKey string         `json:"sshPrivateKey"`
+	Config        map[string]any `json:"config"`
+}
+
+func (s *Server) handleDodoAppInstall(w http.ResponseWriter, r *http.Request) {
+	var req dodoAppInstallReq
+	// TODO(gio): validate that no internal fields are overridden by request
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	clusters, err := s.m.GetClusters()
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	req.Config["clusters"] = installer.ToAccessConfigs(clusters)
+	var cfg bytes.Buffer
+	if err := json.NewEncoder(&cfg).Encode(req.Config); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	app, err := installer.NewDodoApp(cfg.Bytes())
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	appDir := filepath.Join("/dodo-app", req.Id)
+	namespace := "dodo-app-test" // TODO(gio)
+	if _, err := s.m.Install(app, req.Id, appDir, namespace, map[string]any{
+		"managerAddr":   "", // TODO(gio)
+		"appId":         req.Id,
+		"sshPrivateKey": req.SSHPrivateKey,
+	}); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
 func (s *Server) handleNetworks(w http.ResponseWriter, r *http.Request) {
 	env, err := s.m.Config()
 	if err != nil {
diff --git a/core/installer/server/dodo-app/schemas/app.schema.json b/core/installer/server/dodo-app/schemas/app.schema.json
index 55b9941..f6f2171 100644
--- a/core/installer/server/dodo-app/schemas/app.schema.json
+++ b/core/installer/server/dodo-app/schemas/app.schema.json
@@ -22,7 +22,16 @@
           "$ref": "#/definitions/nodejs"
         }
       ]
-    }
+    },
+	"volume": {
+	  "$ref": "#/definitions/volumes"
+	},
+	"postgresql": {
+	  "$ref": "#/definitions/postgresql"
+	},
+	"mongodb": {
+	  "$ref": "#/definitions/mongodb"
+	}
   },
   "definitions": {
     "golang": {
@@ -45,15 +54,6 @@
         "ingress": {
           "$ref": "#/definitions/ingress"
         },
-        "volumes": {
-          "$ref": "#/definitions/volumes"
-        },
-        "postgresql": {
-          "$ref": "#/definitions/postgresql"
-        },
-        "mongodb": {
-          "$ref": "#/definitions/mongodb"
-        },
         "dev": {
           "$ref": "#/definitions/dev"
         }
@@ -100,15 +100,6 @@
         "ingress": {
           "$ref": "#/definitions/ingress"
         },
-        "volumes": {
-          "$ref": "#/definitions/volumes"
-        },
-        "postgresql": {
-          "$ref": "#/definitions/postgresql"
-        },
-        "mongodb": {
-          "$ref": "#/definitions/mongodb"
-        },
         "dev": {
           "$ref": "#/definitions/dev"
         }
@@ -132,15 +123,6 @@
         "ingress": {
           "$ref": "#/definitions/ingress"
         },
-        "volumes": {
-          "$ref": "#/definitions/volumes"
-        },
-        "postgresql": {
-          "$ref": "#/definitions/postgresql"
-        },
-        "mongodb": {
-          "$ref": "#/definitions/mongodb"
-        },
         "dev": {
           "$ref": "#/definitions/dev"
         },
@@ -167,15 +149,6 @@
         "ingress": {
           "$ref": "#/definitions/ingress"
         },
-        "volumes": {
-          "$ref": "#/definitions/volumes"
-        },
-        "postgresql": {
-          "$ref": "#/definitions/postgresql"
-        },
-        "mongodb": {
-          "$ref": "#/definitions/mongodb"
-        },
         "dev": {
           "$ref": "#/definitions/dev"
         },
diff --git a/core/installer/server/dodo-app/server.go b/core/installer/server/dodo-app/server.go
index 7643168..2bbbb7c 100644
--- a/core/installer/server/dodo-app/server.go
+++ b/core/installer/server/dodo-app/server.go
@@ -1439,7 +1439,6 @@
 			"/.dodo/app",
 			namespace,
 			map[string]any{
-				"repoAddr":       repo.FullAddress(),
 				"repoPublicAddr": s.repoPublicAddr,
 				"managerAddr":    fmt.Sprintf("http://%s", s.self),
 				"appId":          name,