Dodo APP: infrastructure to deploy app by pusing to Git repo

Change-Id: I4034c6893255581b014ddb207c844261cb34202b
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
new file mode 100644
index 0000000..5eb2f58
--- /dev/null
+++ b/core/installer/welcome/dodo_app.go
@@ -0,0 +1,122 @@
+package welcome
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/giolekva/pcloud/core/installer"
+	"github.com/giolekva/pcloud/core/installer/soft"
+)
+
+type DodoAppServer struct {
+	port      int
+	sshKey    string
+	client    soft.Client
+	namespace string
+	env       installer.EnvConfig
+	workers   map[string]struct{}
+}
+
+func NewDodoAppServer(
+	port int,
+	sshKey string,
+	client soft.Client,
+	namespace string,
+	env installer.EnvConfig,
+) *DodoAppServer {
+	return &DodoAppServer{
+		port,
+		sshKey,
+		client,
+		namespace,
+		env,
+		map[string]struct{}{},
+	}
+}
+
+func (s *DodoAppServer) Start() error {
+	http.HandleFunc("/update", s.handleUpdate)
+	http.HandleFunc("/register-worker", s.handleRegisterWorker)
+	return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
+}
+
+type updateReq struct {
+	Ref string `json:"ref"`
+}
+
+func (s *DodoAppServer) handleUpdate(w http.ResponseWriter, r *http.Request) {
+	fmt.Println("update")
+	var req updateReq
+	var contents strings.Builder
+	io.Copy(&contents, r.Body)
+	c := contents.String()
+	fmt.Println(c)
+	if err := json.NewDecoder(strings.NewReader(c)).Decode(&req); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if req.Ref != "refs/heads/master" {
+		return
+	}
+	go func() {
+		time.Sleep(20 * time.Second)
+		if err := UpdateDodoApp(s.client, s.namespace, s.sshKey, &s.env); err != nil {
+			fmt.Println(err)
+		}
+	}()
+	for addr, _ := range s.workers {
+		go func() {
+			// TODO(gio): make port configurable
+			http.Get(fmt.Sprintf("http://%s:3000/update", addr))
+		}()
+	}
+}
+
+type registerWorkerReq struct {
+	Address string `json:"address"`
+}
+
+func (s *DodoAppServer) handleRegisterWorker(w http.ResponseWriter, r *http.Request) {
+	var req registerWorkerReq
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	s.workers[req.Address] = struct{}{}
+	fmt.Printf("registered worker: %s\n", req.Address)
+}
+
+func UpdateDodoApp(client soft.Client, namespace string, sshKey string, env *installer.EnvConfig) error {
+	repo, err := client.GetRepo("app")
+	if err != nil {
+		return err
+	}
+	nsCreator := installer.NewNoOpNamespaceCreator()
+	if err != nil {
+		return err
+	}
+	m, err := installer.NewAppManager(repo, nsCreator, "/.dodo")
+	if err != nil {
+		return err
+	}
+	appCfg, err := soft.ReadFile(repo, "app.cue")
+	fmt.Println(string(appCfg))
+	if err != nil {
+		return err
+	}
+	app, err := installer.NewDodoApp(appCfg)
+	if err != nil {
+		return err
+	}
+	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 {
+		return err
+	}
+	return nil
+}
diff --git a/core/installer/welcome/env_test.go b/core/installer/welcome/env_test.go
index e4cee83..0803e64 100644
--- a/core/installer/welcome/env_test.go
+++ b/core/installer/welcome/env_test.go
@@ -59,7 +59,7 @@
 	return nil
 }
 
-func (r mockRepoIO) CommitAndPush(message string) error {
+func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) error {
 	r.t.Logf("Commit and push: %s", message)
 	return nil
 }
@@ -128,6 +128,10 @@
 	return nil
 }
 
+func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
+	return nil
+}
+
 type fakeClientGetter struct {
 	t     *testing.T
 	envFS billy.Filesystem