DodoApp: Filter domains

Change-Id: I475f6b6c9d00ccc0214c54f22c6c8fd428cd996d
diff --git a/charts/dodo-app/templates/install.yaml b/charts/dodo-app/templates/install.yaml
index d63326d..f94202b 100644
--- a/charts/dodo-app/templates/install.yaml
+++ b/charts/dodo-app/templates/install.yaml
@@ -127,6 +127,7 @@
         - --app-admin-key={{ .Values.appAdminKey }}
         - --git-repo-public-key={{ .Values.gitRepoPublicKey }}
         - --db=/dodo-app/db/apps.db
+        - --networks={{ .Values.allowedNetworks }}
         volumeMounts:
         - name: ssh-key
           readOnly: true
diff --git a/charts/dodo-app/values.yaml b/charts/dodo-app/values.yaml
index 4c0b788..abb976a 100644
--- a/charts/dodo-app/values.yaml
+++ b/charts/dodo-app/values.yaml
@@ -14,3 +14,4 @@
 appAdminKey: ""
 gitRepoPublicKey: ""
 persistentVolumeClaimName: ""
+allowedNetworks: ""
diff --git a/core/installer/app_configs/app_global_env.cue b/core/installer/app_configs/app_global_env.cue
index ca7e099..47af9a0 100644
--- a/core/installer/app_configs/app_global_env.cue
+++ b/core/installer/app_configs/app_global_env.cue
@@ -11,21 +11,7 @@
 	network: #EnvNetwork
 }
 
-networks: {
-	public: #Network & {
-		name: "Public"
-		ingressClass: "\(global.pcloudEnvName)-ingress-public"
-		certificateIssuer: "\(global.id)-public"
-		domain: global.domain
-		allocatePortAddr: "http://port-allocator.\(global.pcloudEnvName)-ingress-public.svc.cluster.local/api/allocate"
-	}
-	private: #Network & {
-		name: "Private"
-		ingressClass: "\(global.id)-ingress-private"
-		domain: global.privateDomain
-		allocatePortAddr: "http://port-allocator.\(global.id)-ingress-private.svc.cluster.local/api/allocate"
-	}
-}
+networks: {}
 
 // TODO(gio): remove
 ingressPrivate: "\(global.id)-ingress-private"
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 4fc0b1f..2cc29ed 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -649,6 +649,10 @@
 	}
 }
 
+func WithNoNetworks() InstallOption {
+	return WithNetworks([]Network{})
+}
+
 func WithBranch(branch string) InstallOption {
 	return func(o *installOptions) {
 		o.Branch = branch
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index 2eb5b8f..ea5e7fd 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -28,6 +28,7 @@
 	appAdminKey       string
 	gitRepoPublicKey  string
 	db                string
+	networks          []string
 }
 
 func dodoAppCmd() *cobra.Command {
@@ -101,6 +102,12 @@
 		"",
 		"",
 	)
+	cmd.Flags().StringSliceVar(
+		&dodoAppFlags.networks,
+		"networks",
+		[]string{},
+		"",
+	)
 	return cmd
 }
 
@@ -160,6 +167,7 @@
 		softClient,
 		dodoAppFlags.namespace,
 		dodoAppFlags.envAppManagerAddr,
+		dodoAppFlags.networks,
 		nsc,
 		jc,
 		env,
@@ -168,7 +176,7 @@
 		return err
 	}
 	if dodoAppFlags.appAdminKey != "" {
-		if _, err := s.CreateApp("app", dodoAppFlags.appAdminKey); err != nil {
+		if _, err := s.CreateApp("app", dodoAppFlags.appAdminKey, "Private"); err != nil {
 			return err
 		}
 	}
diff --git a/core/installer/values-tmpl/dodo-app-instance.cue b/core/installer/values-tmpl/dodo-app-instance.cue
index e22f177..e0d6906 100644
--- a/core/installer/values-tmpl/dodo-app-instance.cue
+++ b/core/installer/values-tmpl/dodo-app-instance.cue
@@ -15,7 +15,6 @@
 readme: "Deploy app by pushing to Git repository"
 description: "Deploy app by pushing to Git repository"
 icon: ""
-_domain: "\(input.subdomain).\(input.network.domain)"
 
 resources: {
 	"config-kustomization": {
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index 99d4418..974225f 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -8,7 +8,8 @@
 	network: #Network @name(Network)
 	subdomain: string @name(Subdomain)
 	sshPort: int @name(SSH Port) @role(port)
-	adminKey: string @name(Admin SSH Public Key)
+	adminKey: string | *"" @name(Admin SSH Public Key)
+	allowedNetworks: string | *"" @name(Allowed Networks)
 
 	// TODO(gio): auto generate
 	ssKeys: #SSHKey
@@ -117,6 +118,7 @@
 			appAdminKey: input.adminKey
 			gitRepoPublicKey: input.ssKeys.public
 			persistentVolumeClaimName: volumes.db.name
+			allowedNetworks: input.allowedNetworks
 		}
 	}
 }
diff --git a/core/installer/welcome/dodo-app-tmpl/index.html b/core/installer/welcome/dodo-app-tmpl/index.html
new file mode 100644
index 0000000..e9a9b11
--- /dev/null
+++ b/core/installer/welcome/dodo-app-tmpl/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang='en'>
+	<head>
+		<title>dodo: app</title>
+		<meta charset='utf-8'>
+	</head>
+	<body>
+		{{ range .Apps }}
+		<a href="/{{ . }}">{{ . }}</a>
+		{{ end }}
+	</body>
+</html>
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 20e881d..1c91cbc 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -2,13 +2,16 @@
 
 import (
 	"context"
+	"embed"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"golang.org/x/crypto/bcrypt"
+	"html/template"
 	"io"
 	"io/fs"
 	"net/http"
+	"slices"
 	"strings"
 	"sync"
 
@@ -19,6 +22,9 @@
 	"github.com/gorilla/securecookie"
 )
 
+//go:embed dodo-app-tmpl/*
+var dodoAppTmplFS embed.FS
+
 const (
 	ConfigRepoName = "config"
 	namespacesFile = "/namespaces.json"
@@ -28,6 +34,18 @@
 	userCtx        = "user"
 )
 
+type dodoAppTmplts struct {
+	index *template.Template
+}
+
+func parseTemplatesDodoApp(fs embed.FS) (dodoAppTmplts, error) {
+	index, err := template.New("index.html").ParseFS(fs, "dodo-app-tmpl/index.html")
+	if err != nil {
+		return dodoAppTmplts{}, err
+	}
+	return dodoAppTmplts{index}, nil
+}
+
 type DodoAppServer struct {
 	l                 sync.Locker
 	st                Store
@@ -39,12 +57,14 @@
 	client            soft.Client
 	namespace         string
 	envAppManagerAddr string
+	networks          []string
 	env               installer.EnvConfig
 	nsc               installer.NamespaceCreator
 	jc                installer.JobCreator
 	workers           map[string]map[string]struct{}
 	appNs             map[string]string
 	sc                *securecookie.SecureCookie
+	tmplts            dodoAppTmplts
 }
 
 // TODO(gio): Initialize appNs on startup
@@ -58,10 +78,15 @@
 	client soft.Client,
 	namespace string,
 	envAppManagerAddr string,
+	networks []string,
 	nsc installer.NamespaceCreator,
 	jc installer.JobCreator,
 	env installer.EnvConfig,
 ) (*DodoAppServer, error) {
+	tmplts, err := parseTemplatesDodoApp(dodoAppTmplFS)
+	if err != nil {
+		return nil, err
+	}
 	sc := securecookie.New(
 		securecookie.GenerateRandomKey(64),
 		securecookie.GenerateRandomKey(32),
@@ -77,12 +102,14 @@
 		client,
 		namespace,
 		envAppManagerAddr,
+		networks,
 		env,
 		nsc,
 		jc,
 		map[string]map[string]struct{}{},
 		map[string]string{},
 		sc,
+		tmplts,
 	}
 	config, err := client.GetRepo(ConfigRepoName)
 	if err != nil {
@@ -223,6 +250,10 @@
 	http.Redirect(w, r, fmt.Sprintf("/%s", appName), http.StatusSeeOther)
 }
 
+type statusData struct {
+	Apps []string
+}
+
 func (s *DodoAppServer) handleStatus(w http.ResponseWriter, r *http.Request) {
 	user := r.Context().Value(userCtx)
 	if user == nil {
@@ -234,8 +265,10 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	for _, a := range apps {
-		fmt.Fprintf(w, "%s\n", a)
+	data := statusData{apps}
+	if err := s.tmplts.index.Execute(w, data); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
 	}
 }
 
@@ -272,7 +305,7 @@
 	c := contents.String()
 	fmt.Println(c)
 	if err := json.NewDecoder(strings.NewReader(c)).Decode(&req); err != nil {
-		fmt.Println(err)
+		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
 	if req.Ref != "refs/heads/master" || req.Repository.Name == ConfigRepoName {
@@ -280,7 +313,7 @@
 	}
 	// TODO(gio): Create commit record on app init as well
 	go func() {
-		networks, err := getNetworks(fmt.Sprintf("%s/api/networks", s.envAppManagerAddr))
+		networks, err := s.getNetworks()
 		if err != nil {
 			return
 		}
@@ -326,6 +359,7 @@
 
 type apiCreateAppReq struct {
 	AdminPublicKey string `json:"adminPublicKey"`
+	NetworkName    string `json:"networkName"`
 }
 
 type apiCreateAppResp struct {
@@ -345,7 +379,7 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	password, err := s.CreateApp(appName, req.AdminPublicKey)
+	password, err := s.CreateApp(appName, req.AdminPublicKey, req.NetworkName)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
@@ -360,7 +394,7 @@
 	}
 }
 
-func (s *DodoAppServer) CreateApp(appName, adminPublicKey string) (string, error) {
+func (s *DodoAppServer) CreateApp(appName, adminPublicKey, networkName string) (string, error) {
 	s.l.Lock()
 	defer s.l.Unlock()
 	fmt.Printf("Creating app: %s\n", appName)
@@ -373,11 +407,7 @@
 	if err != nil {
 		return "", err
 	}
-	if user != "" {
-		if err := s.client.AddPublicKey(user, adminPublicKey); err != nil {
-			return "", err
-		}
-	} else {
+	if user == "" {
 		user = appName
 		if err := s.client.AddUser(user, adminPublicKey); err != nil {
 			return "", err
@@ -409,7 +439,7 @@
 	if err != nil {
 		return "", err
 	}
-	if err := InitRepo(appRepo); err != nil {
+	if err := InitRepo(appRepo, networkName); err != nil {
 		return "", err
 	}
 	apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
@@ -424,7 +454,7 @@
 	}
 	namespace := fmt.Sprintf("%s%s%s", s.env.NamespacePrefix, app.Namespace(), suffix)
 	s.appNs[appName] = namespace
-	networks, err := getNetworks(fmt.Sprintf("%s/api/networks", s.envAppManagerAddr))
+	networks, err := s.getNetworks()
 	if err != nil {
 		return "", err
 	}
@@ -460,7 +490,7 @@
 				"gitRepoPublicKey": s.gitRepoPublicKey,
 			},
 			installer.WithConfig(&s.env),
-			installer.WithNetworks(networks),
+			installer.WithNoNetworks(),
 			installer.WithNoPublish(),
 			installer.WithNoLock(),
 		); err != nil {
@@ -593,14 +623,14 @@
 	type: "golang:1.22.0"
 	run: "main.go"
 	ingress: {
-		network: "Private" // or Public
+		network: "%s"
 		subdomain: "testapp"
 		auth: enabled: false
 	}
 }
 `
 
-func InitRepo(repo soft.RepoIO) error {
+func InitRepo(repo soft.RepoIO, networkName string) error {
 	return repo.Do(func(fs soft.RepoFS) (string, error) {
 		{
 			w, err := fs.Writer("go.mod")
@@ -624,7 +654,7 @@
 				return "", err
 			}
 			defer w.Close()
-			fmt.Fprint(w, appCue)
+			fmt.Fprintf(w, appCue, networkName)
 		}
 		return "go web app template", nil
 	})
@@ -634,14 +664,24 @@
 	return "foo"
 }
 
-func getNetworks(addr string) ([]installer.Network, error) {
+func (s *DodoAppServer) getNetworks() ([]installer.Network, error) {
+	addr := fmt.Sprintf("%s/api/networks", s.envAppManagerAddr)
 	resp, err := http.Get(addr)
 	if err != nil {
 		return nil, err
 	}
-	ret := []installer.Network{}
-	if json.NewDecoder(resp.Body).Decode(&ret); err != nil {
+	networks := []installer.Network{}
+	if json.NewDecoder(resp.Body).Decode(&networks); err != nil {
 		return nil, err
 	}
+	if len(s.networks) == 0 {
+		return networks, nil
+	}
+	ret := []installer.Network{}
+	for _, n := range networks {
+		if slices.Contains(s.networks, n.Name) {
+			ret = append(ret, n)
+		}
+	}
 	return ret, nil
 }