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/tasks/env.go b/core/installer/tasks/env.go
index 7eaf0da..68fbe57 100644
--- a/core/installer/tasks/env.go
+++ b/core/installer/tasks/env.go
@@ -12,16 +12,17 @@
 )
 
 type state struct {
-	infoListener EnvInfoListener
-	publicIPs    []net.IP
-	nsCreator    installer.NamespaceCreator
-	repo         installer.RepoIO
-	ssAdminKeys  *keygen.KeyPair
-	ssClient     *soft.Client
-	fluxUserName string
-	keys         *keygen.KeyPair
-	appManager   *installer.AppManager
-	appsRepo     installer.AppRepository
+	infoListener    EnvInfoListener
+	publicIPs       []net.IP
+	nsCreator       installer.NamespaceCreator
+	repo            installer.RepoIO
+	ssAdminKeys     *keygen.KeyPair
+	ssClient        *soft.Client
+	fluxUserName    string
+	keys            *keygen.KeyPair
+	appManager      *installer.AppManager
+	appsRepo        installer.AppRepository
+	infraAppManager *installer.InfraAppManager
 }
 
 type Env struct {
@@ -46,13 +47,15 @@
 	startIP net.IP,
 	nsCreator installer.NamespaceCreator,
 	repo installer.RepoIO,
+	mgr *installer.InfraAppManager,
 	infoListener EnvInfoListener,
 ) (Task, DNSZoneRef) {
 	st := state{
-		infoListener: infoListener,
-		publicIPs:    publicIPs,
-		nsCreator:    nsCreator,
-		repo:         repo,
+		infoListener:    infoListener,
+		publicIPs:       publicIPs,
+		nsCreator:       nsCreator,
+		repo:            repo,
+		infraAppManager: mgr,
 	}
 	t := newSequentialParentTask(
 		"Create env",
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index d718428..fb3047d 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -64,16 +64,14 @@
 		r.Atomic(func(r installer.RepoFS) (string, error) {
 			{
 				// TODO(giolekva): private domain can be configurable as well
-				config := installer.Config{
-					Values: installer.Values{
-						PCloudEnvName:   env.PCloudEnvName,
-						Id:              env.Name,
-						ContactEmail:    env.ContactEmail,
-						Domain:          env.Domain,
-						PrivateDomain:   fmt.Sprintf("p.%s", env.Domain),
-						PublicIP:        st.publicIPs[0].String(),
-						NamespacePrefix: fmt.Sprintf("%s-", env.Name),
-					},
+				config := installer.AppEnvConfig{
+					Id:              env.Name,
+					InfraName:       env.PCloudEnvName,
+					Domain:          env.Domain,
+					PrivateDomain:   fmt.Sprintf("p.%s", env.Domain),
+					ContactEmail:    env.ContactEmail,
+					PublicIP:        st.publicIPs,
+					NamespacePrefix: fmt.Sprintf("%s-", env.Name),
 				}
 				if err := installer.WriteYaml(r, "config.yaml", config); err != nil {
 					return "", err
@@ -94,7 +92,7 @@
   interval: 1m0s
   url: https://github.com/giolekva/pcloud
   ref:
-    branch: ingress-port-allocator
+    branch: main
 `, env.Name)
 			if err != nil {
 				return "", err
@@ -166,14 +164,15 @@
 		{
 			ingressPrivateIP := startAddr
 			headscaleIP := ingressPrivateIP.Next()
-			app, err := st.appsRepo.Find("metallb-ipaddresspool")
+			app, err := installer.FindEnvApp(st.appsRepo, "metallb-ipaddresspool")
 			if err != nil {
 				return err
 			}
 			{
-				appDir := fmt.Sprintf("/apps/%s-ingress-private", app.Name())
+				instanceId := fmt.Sprintf("%s-ingress-private", app.Name())
+				appDir := fmt.Sprintf("/apps/%s", instanceId)
 				namespace := fmt.Sprintf("%s%s-ingress-private", env.NamespacePrefix, app.Namespace())
-				if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+				if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 					"name":       fmt.Sprintf("%s-ingress-private", env.Name),
 					"from":       ingressPrivateIP.String(),
 					"to":         ingressPrivateIP.String(),
@@ -184,9 +183,10 @@
 				}
 			}
 			{
-				appDir := fmt.Sprintf("/apps/%s-headscale", app.Name())
+				instanceId := fmt.Sprintf("%s-headscale", app.Name())
+				appDir := fmt.Sprintf("/apps/%s", instanceId)
 				namespace := fmt.Sprintf("%s%s-ingress-private", env.NamespacePrefix, app.Namespace())
-				if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+				if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 					"name":       fmt.Sprintf("%s-headscale", env.Name),
 					"from":       headscaleIP.String(),
 					"to":         headscaleIP.String(),
@@ -197,9 +197,10 @@
 				}
 			}
 			{
-				appDir := fmt.Sprintf("/apps/%s", app.Name())
+				instanceId := app.Name()
+				appDir := fmt.Sprintf("/apps/%s", instanceId)
 				namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-				if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+				if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 					"name":       env.Name,
 					"from":       fromIP.String(),
 					"to":         toIP.String(),
@@ -222,13 +223,14 @@
 			if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
 				return err
 			}
-			app, err := st.appsRepo.Find("private-network")
+			app, err := installer.FindEnvApp(st.appsRepo, "private-network")
 			if err != nil {
 				return err
 			}
-			appDir := fmt.Sprintf("/apps/%s", app.Name())
+			instanceId := app.Name()
+			appDir := fmt.Sprintf("/apps/%s", instanceId)
 			namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-			if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+			if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 				"privateNetwork": map[string]any{
 					"hostname": "private-network-proxy",
 					"username": "private-network-proxy",
@@ -246,25 +248,27 @@
 
 func SetupCertificateIssuers(env Env, st *state) Task {
 	pub := newLeafTask(fmt.Sprintf("Public %s", env.Domain), func() error {
-		app, err := st.appsRepo.Find("certificate-issuer-public")
+		app, err := installer.FindEnvApp(st.appsRepo, "certificate-issuer-public")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{}); err != nil {
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{}); err != nil {
 			return err
 		}
 		return nil
 	})
 	priv := newLeafTask(fmt.Sprintf("Private p.%s", env.Domain), func() error {
-		app, err := st.appsRepo.Find("certificate-issuer-private")
+		app, err := installer.FindEnvApp(st.appsRepo, "certificate-issuer-private")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"apiConfigMap": map[string]any{
 				"name":      "api-config", // TODO(gio): take from global pcloud config
 				"namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
@@ -279,13 +283,14 @@
 
 func SetupAuth(env Env, st *state) Task {
 	t := newLeafTask("Setup", func() error {
-		app, err := st.appsRepo.Find("core-auth")
+		app, err := installer.FindEnvApp(st.appsRepo, "core-auth")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
 		}); err != nil {
 			return err
@@ -302,13 +307,14 @@
 
 func SetupGroupMemberships(env Env, st *state) Task {
 	t := newLeafTask("Setup", func() error {
-		app, err := st.appsRepo.Find("memberships")
+		app, err := installer.FindEnvApp(st.appsRepo, "memberships")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"authGroups": strings.Join(initGroups, ","),
 		}); err != nil {
 			return err
@@ -325,13 +331,14 @@
 
 func SetupHeadscale(env Env, startIP net.IP, st *state) Task {
 	t := newLeafTask("Setup", func() error {
-		app, err := st.appsRepo.Find("headscale")
+		app, err := installer.FindEnvApp(st.appsRepo, "headscale")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"subdomain": "headscale",
 			"ipSubnet":  fmt.Sprintf("%s/24", startIP),
 		}); err != nil {
@@ -360,13 +367,14 @@
 		if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
 			return err
 		}
-		app, err := st.appsRepo.Find("welcome")
+		app, err := installer.FindEnvApp(st.appsRepo, "welcome")
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"repoAddr":      st.ssClient.GetRepoAddress("config"),
 			"sshPrivateKey": string(keys.RawPrivateKey()),
 		}); err != nil {
@@ -395,13 +403,14 @@
 		if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
 			return err
 		}
-		app, err := st.appsRepo.Find("app-manager") // TODO(giolekva): configure
+		app, err := installer.FindEnvApp(st.appsRepo, "app-manager") // TODO(giolekva): configure
 		if err != nil {
 			return err
 		}
-		appDir := fmt.Sprintf("/apps/%s", app.Name())
+		instanceId := app.Name()
+		appDir := fmt.Sprintf("/apps/%s", instanceId)
 		namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
-		if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+		if err := st.appManager.Install(app, instanceId, appDir, namespace, map[string]any{
 			"repoAddr":      st.ssClient.GetRepoAddress("config"),
 			"sshPrivateKey": string(keys.RawPrivateKey()),
 			"authGroups":    strings.Join(initGroups, ","),
diff --git a/core/installer/tasks/init.go b/core/installer/tasks/init.go
index 4ed7176..70b7939 100644
--- a/core/installer/tasks/init.go
+++ b/core/installer/tasks/init.go
@@ -38,31 +38,25 @@
 func NewCreateConfigRepoTask(env Env, st *state) Task {
 	t := newLeafTask("Install Git server", func() error {
 		appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
-		ssApp, err := appsRepo.Find("config-repo")
+		app, err := installer.FindInfraApp(appsRepo, "config-repo")
 		if err != nil {
 			return err
 		}
-		ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", env.Name))
+		adminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", env.Name))
 		if err != nil {
 			return err
 		}
-		st.ssAdminKeys = ssAdminKeys
-		ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", env.Name))
+		st.ssAdminKeys = adminKeys
+		keys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", env.Name))
 		if err != nil {
 			return err
 		}
-		derived := installer.Derived{
-			Global: installer.Values{
-				Id:            env.Name,
-				PCloudEnvName: env.PCloudEnvName,
-			},
-			Values: map[string]any{
-				"privateKey": string(ssKeys.RawPrivateKey()),
-				"publicKey":  string(ssKeys.RawAuthorizedKey()),
-				"adminKey":   string(ssAdminKeys.RawAuthorizedKey()),
-			},
-		}
-		return installer.InstallApp(st.repo, st.nsCreator, ssApp, filepath.Join("/environments", env.Name, "config-repo"), env.Name, derived.Values, derived)
+		appDir := filepath.Join("/environments", env.Name, "config-repo")
+		return st.infraAppManager.Install(app, appDir, env.Name, map[string]any{
+			"privateKey": string(keys.RawPrivateKey()),
+			"publicKey":  string(keys.RawAuthorizedKey()),
+			"adminKey":   string(adminKeys.RawAuthorizedKey()),
+		})
 	})
 	return &t
 }