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/bootstrapper.go b/core/installer/bootstrapper.go
index ad01140..6b65096 100644
--- a/core/installer/bootstrapper.go
+++ b/core/installer/bootstrapper.go
@@ -34,21 +34,33 @@
 	return Bootstrapper{cl, ns, ha, appRepo}
 }
 
-func (b Bootstrapper) Run(env EnvConfig) error {
-	if err := b.ns.Create(env.Name); err != nil {
+func (b Bootstrapper) findApp(name string) (InfraApp, error) {
+	app, err := b.appRepo.Find(name)
+	if err != nil {
+		return nil, err
+	}
+	if a, ok := app.(InfraApp); ok {
+		return a, nil
+	} else {
+		return nil, fmt.Errorf("not found")
+	}
+}
+
+func (b Bootstrapper) Run(env BootstrapConfig) error {
+	if err := b.ns.Create(env.InfraName); err != nil {
 		return err
 	}
 	if err := b.installMetallb(env); err != nil {
 		return err
 	}
-	if err := b.installLonghorn(env.Name, env.StorageDir, env.VolumeDefaultReplicaCount); err != nil {
+	if err := b.installLonghorn(env.InfraName, env.StorageDir, env.VolumeDefaultReplicaCount); err != nil {
 		return err
 	}
 	bootstrapJobKeys, err := NewSSHKeyPair("bootstrapper")
 	if err != nil {
 		return err
 	}
-	if err := b.installSoftServe(bootstrapJobKeys.AuthorizedKey(), env.Name, env.ServiceIPs.ConfigRepo); err != nil {
+	if err := b.installSoftServe(bootstrapJobKeys.AuthorizedKey(), env.InfraName, env.ServiceIPs.ConfigRepo); err != nil {
 		return err
 	}
 	time.Sleep(30 * time.Second)
@@ -67,7 +79,7 @@
 	if ss.AddPublicKey("admin", string(env.AdminPublicKey)); err != nil {
 		return err
 	}
-	if err := b.installFluxcd(ss, env.Name); err != nil {
+	if err := b.installFluxcd(ss, env.InfraName); err != nil {
 		return err
 	}
 	fmt.Println("Fluxcd installed")
@@ -80,35 +92,43 @@
 	if err != nil {
 		return err
 	}
+	mgr, err := NewInfraAppManager(repoIO, b.ns)
+	if err != nil {
+		return err
+	}
 	fmt.Println("Configuring main repo")
 	if err := configureMainRepo(repoIO, env); err != nil {
 		return err
 	}
 	fmt.Println("Installing infrastructure services")
-	if err := b.installInfrastructureServices(repoIO, env); err != nil {
+	if err := b.installInfrastructureServices(mgr, env); err != nil {
+		return err
+	}
+	fmt.Println("Installing public ingress")
+	if err := b.installIngressPublic(mgr, ss, env); err != nil {
 		return err
 	}
 	fmt.Println("Installing DNS Zone Manager")
-	if err := b.installDNSZoneManager(repoIO, env); err != nil {
+	if err := b.installDNSZoneManager(mgr, env); err != nil {
 		return err
 	}
 	fmt.Println("Installing Fluxcd Reconciler")
-	if err := b.installFluxcdReconciler(repoIO, ss, env); err != nil {
+	if err := b.installFluxcdReconciler(mgr, ss, env); err != nil {
 		return err
 	}
 	fmt.Println("Installing env manager")
-	if err := b.installEnvManager(repoIO, ss, env); err != nil {
+	if err := b.installEnvManager(mgr, ss, env); err != nil {
 		return err
 	}
 	fmt.Println("Installing Ory Hydra Maester")
-	if err := b.installOryHydraMaester(repoIO, env); err != nil {
+	if err := b.installOryHydraMaester(mgr, env); err != nil {
 		return err
 	}
 	fmt.Println("Environment ready to use")
 	return nil
 }
 
-func (b Bootstrapper) installMetallb(env EnvConfig) error {
+func (b Bootstrapper) installMetallb(env BootstrapConfig) error {
 	if err := b.installMetallbNamespace(env); err != nil {
 		return err
 	}
@@ -127,9 +147,9 @@
 	return nil
 }
 
-func (b Bootstrapper) installMetallbNamespace(env EnvConfig) error {
+func (b Bootstrapper) installMetallbNamespace(env BootstrapConfig) error {
 	fmt.Println("Installing metallb namespace")
-	config, err := b.ha.New(env.Name)
+	config, err := b.ha.New(env.InfraName)
 	if err != nil {
 		return err
 	}
@@ -146,7 +166,7 @@
 		},
 	}
 	installer := action.NewInstall(config)
-	installer.Namespace = env.Name
+	installer.Namespace = env.InfraName
 	installer.ReleaseName = "metallb-ns"
 	installer.Wait = true
 	installer.WaitForJobs = true
@@ -395,26 +415,21 @@
 	return nil
 }
 
-func (b Bootstrapper) installInfrastructureServices(repo RepoIO, env EnvConfig) error {
+func (b Bootstrapper) installInfrastructureServices(mgr *InfraAppManager, env BootstrapConfig) error {
 	install := func(name string) error {
 		fmt.Printf("Installing infrastructure service %s\n", name)
-		app, err := b.appRepo.Find(name)
+		app, err := b.findApp(name)
 		if err != nil {
 			return err
 		}
-		namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
-		derived := Derived{
-			Global: Values{
-				PCloudEnvName: env.Name,
-			},
-		}
-		return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
+		namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+		appDir := filepath.Join("/infrastructure", app.Name())
+		return mgr.Install(app, appDir, namespace, map[string]any{})
 	}
 	appsToInstall := []string{
 		"resource-renderer-controller",
 		"headscale-controller",
 		"csi-driver-smb",
-		"ingress-public",
 		"cert-manager",
 	}
 	for _, name := range appsToInstall {
@@ -425,9 +440,18 @@
 	return nil
 }
 
-func configureMainRepo(repo RepoIO, env EnvConfig) error {
+func configureMainRepo(repo RepoIO, bootstrap BootstrapConfig) error {
 	return repo.Atomic(func(r RepoFS) (string, error) {
-		if err := WriteYaml(r, "config.yaml", env); err != nil {
+		if err := WriteYaml(r, "bootstrap-config.yaml", bootstrap); err != nil {
+			return "", err
+		}
+		infra := InfraConfig{
+			Name:                 bootstrap.InfraName,
+			PublicIP:             bootstrap.PublicIP,
+			InfraNamespacePrefix: bootstrap.NamespacePrefix,
+			InfraAdminPublicKey:  bootstrap.AdminPublicKey,
+		}
+		if err := WriteYaml(r, "config.yaml", infra); err != nil {
 			return "", err
 		}
 		if err := WriteYaml(r, "env-cidrs.yaml", EnvCIDRs{}); err != nil {
@@ -435,7 +459,7 @@
 		}
 		kust := NewKustomization()
 		kust.AddResources(
-			fmt.Sprintf("%s-flux", env.Name),
+			fmt.Sprintf("%s-flux", bootstrap.InfraName),
 			"infrastructure",
 			"environments",
 		)
@@ -459,7 +483,7 @@
   url: https://github.com/giolekva/pcloud
   ref:
     branch: main
-`, env.Name)))
+`, bootstrap.InfraName)))
 			if err != nil {
 				return "", err
 			}
@@ -476,89 +500,94 @@
 	})
 }
 
-func (b Bootstrapper) installEnvManager(repo RepoIO, ss *soft.Client, env EnvConfig) error {
+func (b Bootstrapper) installEnvManager(mgr *InfraAppManager, ss *soft.Client, env BootstrapConfig) error {
 	keys, err := NewSSHKeyPair("env-manager")
 	if err != nil {
 		return err
 	}
-	user := fmt.Sprintf("%s-env-manager", env.Name)
+	user := fmt.Sprintf("%s-env-manager", env.InfraName)
 	if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
 		return err
 	}
 	if err := ss.MakeUserAdmin(user); err != nil {
 		return err
 	}
-	app, err := b.appRepo.Find("env-manager")
+	app, err := b.findApp("env-manager")
 	if err != nil {
 		return err
 	}
-	namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
-	derived := Derived{
-		Global: Values{
-			PCloudEnvName: env.Name,
-		},
-		Values: map[string]any{
-			"repoIP":        env.ServiceIPs.ConfigRepo,
-			"repoPort":      22,
-			"repoName":      "config",
-			"sshPrivateKey": string(keys.RawPrivateKey()),
-		},
-	}
-	return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, derived.Values, derived)
+	namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+	appDir := filepath.Join("/infrastructure", app.Name())
+	return mgr.Install(app, appDir, namespace, map[string]any{
+		"repoIP":        env.ServiceIPs.ConfigRepo,
+		"repoPort":      22,
+		"repoName":      "config",
+		"sshPrivateKey": string(keys.RawPrivateKey()),
+	})
 }
 
-func (b Bootstrapper) installOryHydraMaester(repo RepoIO, env EnvConfig) error {
-	app, err := b.appRepo.Find("hydra-maester")
+func (b Bootstrapper) installIngressPublic(mgr *InfraAppManager, ss *soft.Client, env BootstrapConfig) error {
+	keys, err := NewSSHKeyPair("port-allocator")
 	if err != nil {
 		return err
 	}
-	namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
-	derived := Derived{
-		Global: Values{
-			PCloudEnvName: env.Name,
-		},
+	user := fmt.Sprintf("%s-port-allocator", env.InfraName)
+	if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
+		return err
 	}
-	return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
+	if err := ss.AddReadWriteCollaborator("config", user); err != nil {
+		return err
+	}
+	app, err := b.findApp("ingress-public")
+	if err != nil {
+		return err
+	}
+	namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+	appDir := filepath.Join("/infrastructure", app.Name())
+	return mgr.Install(app, appDir, namespace, map[string]any{
+		"sshPrivateKey": string(keys.RawPrivateKey()),
+	})
 }
 
-func (b Bootstrapper) installDNSZoneManager(repo RepoIO, env EnvConfig) error {
+func (b Bootstrapper) installOryHydraMaester(mgr *InfraAppManager, env BootstrapConfig) error {
+	app, err := b.findApp("hydra-maester")
+	if err != nil {
+		return err
+	}
+	namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+	appDir := filepath.Join("/infrastructure", app.Name())
+	return mgr.Install(app, appDir, namespace, map[string]any{})
+}
+
+func (b Bootstrapper) installDNSZoneManager(mgr *InfraAppManager, env BootstrapConfig) error {
 	const (
 		volumeClaimName = "dns-zone-configs"
 		volumeMountPath = "/etc/pcloud/dns-zone-configs"
 	)
-	app, err := b.appRepo.Find("dns-zone-manager")
+	app, err := b.findApp("dns-zone-manager")
 	if err != nil {
 		return err
 	}
-	namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
-	derived := Derived{
-		Global: Values{
-			PCloudEnvName: env.Name,
+	namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+	appDir := filepath.Join("/infrastructure", app.Name())
+	return mgr.Install(app, appDir, namespace, map[string]any{
+		"volume": map[string]any{
+			"claimName": volumeClaimName,
+			"mountPath": volumeMountPath,
+			"size":      "1Gi",
 		},
-		Values: map[string]any{
-			"volume": map[string]any{
-				"claimName": volumeClaimName,
-				"mountPath": volumeMountPath,
-				"size":      "1Gi",
-			},
-			"apiConfigMapName": dnsAPIConfigMapName,
-		},
-	}
-	return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, derived.Values, derived)
+		"apiConfigMapName": dnsAPIConfigMapName,
+	})
 }
 
-func (b Bootstrapper) installFluxcdReconciler(repo RepoIO, ss *soft.Client, env EnvConfig) error {
-	app, err := b.appRepo.Find("fluxcd-reconciler")
+func (b Bootstrapper) installFluxcdReconciler(mgr *InfraAppManager, ss *soft.Client, env BootstrapConfig) error {
+	app, err := b.findApp("fluxcd-reconciler")
 	if err != nil {
 		return err
 	}
-	namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
-	derived := Derived{
-		Global: Values{
-			PCloudEnvName: env.Name,
-		},
-	}
-	return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
+	namespace := fmt.Sprintf("%s-%s", env.InfraName, app.Namespace())
+	appDir := filepath.Join("/infrastructure", app.Name())
+	return mgr.Install(app, appDir, namespace, map[string]any{})
 }
 
 type HelmActionConfigFactory interface {