Installer: Separate infrastructure and environment apps.

Have two separate application managers, one for installing apps on the
dodo infra, and nother installing on individual environments.

Change-Id: I1b24f008e30c5533c48c22ea92328bc4bb7abc54
diff --git a/core/installer/welcome/appmanager-tmpl/app.html b/core/installer/welcome/appmanager-tmpl/app.html
index c1ae84a..8c25adf 100644
--- a/core/installer/welcome/appmanager-tmpl/app.html
+++ b/core/installer/welcome/appmanager-tmpl/app.html
@@ -75,7 +75,7 @@
 
 <form id="config-form">
     {{ if $instance }}
-      {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" ($instance.Input $schema)) }}
+      {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" ($instance.InputToValues $schema)) }}
     {{ else }}
       {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }}
     {{ end }}
@@ -93,7 +93,7 @@
   {{ if or (not $instance) (ne $instance.Id .Id)}}
     <details>
       <summary>{{ .Id }}</summary>
-      {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" true "Data" (.Input $schema) ) }}
+      {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" true "Data" (.InputToValues $schema)) }}
       <a href="/instance/{{ .Id }}" role="button" class="secondary">View</a>
     </details>
   {{ end }}
@@ -139,7 +139,7 @@
 
 <script>
  let readme = "";
- let config = {{ if $instance }}JSON.parse({{ toJson ($instance.Input $schema) }}){{ else }}{}{{ end }};
+ let config = {{ if $instance }}JSON.parse({{ toJson ($instance.InputToValues $schema) }}){{ else }}{}{{ end }};
 
  function setValue(name, value, config) {
   let items = name.split(".")
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index b26a001..8a4bf34 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -66,11 +66,11 @@
 }
 
 type app struct {
-	Name             string                `json:"name"`
-	Icon             template.HTML         `json:"icon"`
-	ShortDescription string                `json:"shortDescription"`
-	Slug             string                `json:"slug"`
-	Instances        []installer.AppConfig `json:"instances,omitempty"`
+	Name             string                        `json:"name"`
+	Icon             template.HTML                 `json:"icon"`
+	ShortDescription string                        `json:"shortDescription"`
+	Slug             string                        `json:"slug"`
+	Instances        []installer.AppInstanceConfig `json:"instances,omitempty"`
 }
 
 func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
@@ -95,25 +95,6 @@
 	if err != nil {
 		return err
 	}
-	for _, instance := range instances {
-		values, ok := instance.Config["Values"].(map[string]any)
-		if !ok {
-			return fmt.Errorf("Expected map")
-		}
-		for k, v := range values {
-			if k == "Network" {
-				n, ok := v.(map[string]any)
-				if !ok {
-					return fmt.Errorf("Expected map")
-				}
-				values["Network"], ok = n["Name"]
-				if !ok {
-					return fmt.Errorf("Missing Name")
-				}
-				break
-			}
-		}
-	}
 	return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Name(), instances})
 }
 
@@ -123,28 +104,11 @@
 	if err != nil {
 		return err
 	}
-	values, ok := instance.Config["Values"].(map[string]any)
-	if !ok {
-		return fmt.Errorf("Expected map")
-	}
-	for k, v := range values {
-		if k == "Network" {
-			n, ok := v.(map[string]any)
-			if !ok {
-				return fmt.Errorf("Expected map")
-			}
-			values["Network"], ok = n["Name"]
-			if !ok {
-				return fmt.Errorf("Missing Name")
-			}
-			break
-		}
-	}
 	a, err := s.r.Find(instance.AppId)
 	if err != nil {
 		return err
 	}
-	return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Name(), []installer.AppConfig{instance}})
+	return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Name(), []installer.AppInstanceConfig{instance}})
 }
 
 type file struct {
@@ -162,7 +126,7 @@
 	if err != nil {
 		return err
 	}
-	global, err := s.m.Config()
+	env, err := s.m.Config()
 	if err != nil {
 		return err
 	}
@@ -170,22 +134,11 @@
 	if err := json.Unmarshal(contents, &values); err != nil {
 		return err
 	}
-	if network, ok := values["network"]; ok {
-		for _, n := range installer.CreateNetworks(global) {
-			if n.Name == network { // TODO(giolekva): handle not found
-				values["network"] = n
-			}
-		}
-	}
-	all := installer.Derived{
-		Global: global.Values,
-		Values: values,
-	}
-	a, err := s.r.Find(slug)
+	a, err := installer.FindEnvApp(s.r, slug)
 	if err != nil {
 		return err
 	}
-	r, err := a.Render(all)
+	r, err := a.Render(installer.Release{}, env, values)
 	if err != nil {
 		return err
 	}
@@ -212,24 +165,25 @@
 		return err
 	}
 	log.Printf("Values: %+v\n", values)
-	a, err := s.r.Find(slug)
+	a, err := installer.FindEnvApp(s.r, slug)
 	if err != nil {
 		return err
 	}
 	log.Printf("Found application: %s\n", slug)
-	config, err := s.m.Config()
+	env, err := s.m.Config()
 	if err != nil {
 		return err
 	}
-	log.Printf("Configuration: %+v\n", config)
+	log.Printf("Configuration: %+v\n", env)
 	suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
 	suffix, err := suffixGen.Generate()
 	if err != nil {
 		return err
 	}
-	appDir := fmt.Sprintf("/apps/%s%s", a.Name(), suffix)
-	namespace := fmt.Sprintf("%s%s%s", config.Values.NamespacePrefix, a.Namespace(), suffix)
-	if err := s.m.Install(a, appDir, namespace, values); err != nil {
+	instanceId := a.Name() + suffix
+	appDir := fmt.Sprintf("/apps/%s", instanceId)
+	namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
+	if err := s.m.Install(a, instanceId, appDir, namespace, values); err != nil {
 		log.Printf("%s\n", err.Error())
 		return err
 	}
@@ -252,7 +206,7 @@
 	if err := json.Unmarshal(contents, &values); err != nil {
 		return err
 	}
-	a, err := s.r.Find(appConfig.AppId)
+	a, err := installer.FindEnvApp(s.r, appConfig.AppId)
 	if err != nil {
 		return err
 	}
@@ -291,9 +245,9 @@
 }
 
 type appContext struct {
-	App               installer.App
-	Instance          *installer.AppConfig
-	Instances         []installer.AppConfig
+	App               installer.EnvApp
+	Instance          *installer.AppInstanceConfig
+	Instances         []installer.AppInstanceConfig
 	AvailableNetworks []installer.Network
 }
 
@@ -311,7 +265,7 @@
 		return err
 	}
 	slug := c.Param("slug")
-	a, err := s.r.Find(slug)
+	a, err := installer.FindEnvApp(s.r, slug)
 	if err != nil {
 		return err
 	}
@@ -346,7 +300,7 @@
 	if err != nil {
 		return err
 	}
-	a, err := s.r.Find(instance.AppId)
+	a, err := installer.FindEnvApp(s.r, instance.AppId)
 	if err != nil {
 		return err
 	}
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index 3efe330..219c67c 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -326,8 +326,13 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	var env installer.EnvConfig
-	if err := installer.ReadYaml(s.repo, "config.yaml", &env); err != nil {
+	mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	var infra installer.InfraConfig
+	if err := installer.ReadYaml(s.repo, "config.yaml", &infra); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -373,20 +378,18 @@
 	}
 	t, dns := tasks.NewCreateEnvTask(
 		tasks.Env{
-			PCloudEnvName:   env.Name,
+			PCloudEnvName:   infra.Name,
 			Name:            req.Name,
 			ContactEmail:    req.ContactEmail,
 			Domain:          req.Domain,
 			AdminPublicKey:  req.AdminPublicKey,
 			NamespacePrefix: fmt.Sprintf("%s-", req.Name),
 		},
-		[]net.IP{
-			net.ParseIP("135.181.48.180"),
-			net.ParseIP("65.108.39.172"),
-		},
+		infra.PublicIP,
 		startIP,
 		s.nsCreator,
 		s.repo,
+		mgr,
 		infoUpdater,
 	)
 	s.tasks[key] = t
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 697fd07..a820586 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -210,21 +210,22 @@
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		config, err := appManager.Config()
+		env, err := appManager.Config()
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
 		appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
 		{
-			app, err := appsRepo.Find("headscale-user")
+			app, err := installer.FindEnvApp(appsRepo, "headscale-user")
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 				return
 			}
-			appDir := fmt.Sprintf("/apps/%s-%s", app.Name(), req.Username)
-			namespace := fmt.Sprintf("%s%s", config.Values.NamespacePrefix, app.Namespace())
-			if err := appManager.Install(app, appDir, namespace, map[string]any{
+			instanceId := fmt.Sprintf("%s-%s", app.Name(), req.Username)
+			appDir := fmt.Sprintf("/apps/%s", instanceId)
+			namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+			if err := appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 				"username": req.Username,
 				"preAuthKey": map[string]any{
 					"enabled": false,