AppManager: cache helm charts and container images to local registry

Caching container images is disabled until we figure out how to run
container registry behind TLS.

Change-Id: I0253f2a862e5adddff18a82b102f67258151c070
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index 5da26ef..a4430b8 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -247,11 +247,6 @@
 		http.Error(w, "empty slug", http.StatusBadRequest)
 		return
 	}
-	appConfig, err := s.m.AppConfig(slug)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
 	contents, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -262,16 +257,11 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	a, err := installer.FindEnvApp(s.r, appConfig.AppId)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
 	if _, ok := s.tasks[slug]; ok {
 		http.Error(w, "Update already in progress", http.StatusBadRequest)
 		return
 	}
-	rr, err := s.m.Update(a, slug, values)
+	rr, err := s.m.Update(slug, values)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
@@ -452,7 +442,7 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	a, err := installer.FindEnvApp(s.r, instance.AppId)
+	a, err := s.m.GetInstanceApp(instance.Id)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 5eb2f58..251a379 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -18,6 +18,7 @@
 	client    soft.Client
 	namespace string
 	env       installer.EnvConfig
+	jc        installer.JobCreator
 	workers   map[string]struct{}
 }
 
@@ -26,6 +27,7 @@
 	sshKey string,
 	client soft.Client,
 	namespace string,
+	jc installer.JobCreator,
 	env installer.EnvConfig,
 ) *DodoAppServer {
 	return &DodoAppServer{
@@ -34,6 +36,7 @@
 		client,
 		namespace,
 		env,
+		jc,
 		map[string]struct{}{},
 	}
 }
@@ -64,7 +67,7 @@
 	}
 	go func() {
 		time.Sleep(20 * time.Second)
-		if err := UpdateDodoApp(s.client, s.namespace, s.sshKey, &s.env); err != nil {
+		if err := UpdateDodoApp(s.client, s.namespace, s.sshKey, s.jc, &s.env); err != nil {
 			fmt.Println(err)
 		}
 	}()
@@ -90,16 +93,17 @@
 	fmt.Printf("registered worker: %s\n", req.Address)
 }
 
-func UpdateDodoApp(client soft.Client, namespace string, sshKey string, env *installer.EnvConfig) error {
+func UpdateDodoApp(client soft.Client, namespace string, sshKey string, jc installer.JobCreator, env *installer.EnvConfig) error {
 	repo, err := client.GetRepo("app")
 	if err != nil {
 		return err
 	}
-	nsCreator := installer.NewNoOpNamespaceCreator()
+	nsc := installer.NewNoOpNamespaceCreator()
 	if err != nil {
 		return err
 	}
-	m, err := installer.NewAppManager(repo, nsCreator, "/.dodo")
+	hf := installer.NewGitHelmFetcher()
+	m, err := installer.NewAppManager(repo, nsc, jc, hf, "/.dodo")
 	if err != nil {
 		return err
 	}
@@ -112,10 +116,11 @@
 	if err != nil {
 		return err
 	}
+	lg := installer.GitRepositoryLocalChartGenerator{"app", namespace}
 	if _, err := m.Install(app, "app", "/.dodo/app", namespace, map[string]any{
 		"repoAddr":      repo.FullAddress(),
 		"sshPrivateKey": sshKey,
-	}, installer.WithConfig(env), installer.WithBranch("dodo")); err != nil {
+	}, installer.WithConfig(env), installer.WithBranch("dodo"), installer.WithLocalChartGenerator(lg)); err != nil {
 		return err
 	}
 	return nil
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index 5d2206f..949cbe0 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -84,6 +84,8 @@
 	repo          soft.RepoIO
 	repoClient    soft.ClientGetter
 	nsCreator     installer.NamespaceCreator
+	jc            installer.JobCreator
+	hf            installer.HelmFetcher
 	dnsFetcher    installer.ZoneStatusFetcher
 	nameGenerator installer.NameGenerator
 	httpClient    phttp.Client
@@ -100,6 +102,8 @@
 	repo soft.RepoIO,
 	repoClient soft.ClientGetter,
 	nsCreator installer.NamespaceCreator,
+	jc installer.JobCreator,
+	hf installer.HelmFetcher,
 	dnsFetcher installer.ZoneStatusFetcher,
 	nameGenerator installer.NameGenerator,
 	httpClient phttp.Client,
@@ -112,6 +116,8 @@
 		repo,
 		repoClient,
 		nsCreator,
+		jc,
+		hf,
 		dnsFetcher,
 		nameGenerator,
 		httpClient,
@@ -333,7 +339,9 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator)
+	hf := installer.NewGitHelmFetcher()
+	lg := installer.NewInfraLocalChartGenerator()
+	mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator, hf, lg)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
@@ -403,6 +411,8 @@
 	t, dns := tasks.NewCreateEnvTask(
 		env,
 		s.nsCreator,
+		s.jc,
+		s.hf,
 		s.dnsFetcher,
 		s.httpClient,
 		s.dnsClient,
diff --git a/core/installer/welcome/env_test.go b/core/installer/welcome/env_test.go
index 0803e64..35e968d 100644
--- a/core/installer/welcome/env_test.go
+++ b/core/installer/welcome/env_test.go
@@ -34,6 +34,24 @@
 	return nil
 }
 
+type fakeJobCreator struct {
+	t *testing.T
+}
+
+func (f fakeJobCreator) Create(name, namespace string, image string, cmd []string) error {
+	f.t.Logf("Create job: %s/%s %s \"%s\"", namespace, name, image, strings.Join(cmd, " "))
+	return nil
+}
+
+type fakeHelmFetcher struct {
+	t *testing.T
+}
+
+func (f fakeHelmFetcher) Pull(chart installer.HelmChartGitRepo, rfs soft.RepoFS, root string) error {
+	f.t.Logf("Helm pull: %+v", chart)
+	return nil
+}
+
 type fakeZoneStatusFetcher struct {
 	t *testing.T
 }
@@ -213,8 +231,11 @@
 	infraFS := memfs.New()
 	envFS := memfs.New()
 	nsCreator := fakeNSCreator{t}
+	jc := fakeJobCreator{t}
+	hf := fakeHelmFetcher{t}
+	lg := installer.GitRepositoryLocalChartGenerator{"foo", "bar"}
 	infraRepo := mockRepoIO{soft.NewBillyRepoFS(infraFS), "foo.bar", t, &sync.Mutex{}}
-	infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator)
+	infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator, hf, lg)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -254,6 +275,8 @@
 		infraRepo,
 		cg,
 		nsCreator,
+		jc,
+		hf,
 		fakeZoneStatusFetcher{t},
 		fixedNameGenerator{},
 		httpClient,
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 64f4cf1..7688d50 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -30,6 +30,7 @@
 	port                int
 	repo                soft.RepoIO
 	nsCreator           installer.NamespaceCreator
+	hf                  installer.HelmFetcher
 	createAccountAddr   string
 	loginAddr           string
 	membershipsInitAddr string
@@ -39,6 +40,7 @@
 	port int,
 	repo soft.RepoIO,
 	nsCreator installer.NamespaceCreator,
+	hf installer.HelmFetcher,
 	createAccountAddr string,
 	loginAddr string,
 	membershipsInitAddr string,
@@ -47,6 +49,7 @@
 		port,
 		repo,
 		nsCreator,
+		hf,
 		createAccountAddr,
 		loginAddr,
 		membershipsInitAddr,
@@ -205,8 +208,9 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
+	// TODO(gio): remove this once auto user sync is implemented
 	{
-		appManager, err := installer.NewAppManager(s.repo, s.nsCreator, "/apps")
+		appManager, err := installer.NewAppManager(s.repo, s.nsCreator, nil, s.hf, "/apps")
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return