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

Change-Id: I4034c6893255581b014ddb207c844261cb34202b
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
new file mode 100644
index 0000000..d9f9a69
--- /dev/null
+++ b/core/installer/cmd/dodo_app.go
@@ -0,0 +1,175 @@
+package main
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/giolekva/pcloud/core/installer"
+	"github.com/giolekva/pcloud/core/installer/soft"
+	"github.com/giolekva/pcloud/core/installer/welcome"
+
+	"github.com/spf13/cobra"
+)
+
+var dodoAppFlags struct {
+	port      int
+	sshKey    string
+	repoAddr  string
+	self      string
+	namespace string
+	envConfig string
+}
+
+func dodoAppCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:  "dodo-app",
+		RunE: dodoAppCmdRun,
+	}
+	cmd.Flags().IntVar(
+		&dodoAppFlags.port,
+		"port",
+		8080,
+		"",
+	)
+	cmd.Flags().StringVar(
+		&dodoAppFlags.repoAddr,
+		"repo-addr",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&dodoAppFlags.sshKey,
+		"ssh-key",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&dodoAppFlags.self,
+		"self",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&dodoAppFlags.namespace,
+		"namespace",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&dodoAppFlags.envConfig,
+		"env-config",
+		"",
+		"",
+	)
+	return cmd
+}
+
+func dodoAppCmdRun(cmd *cobra.Command, args []string) error {
+	envConfig, err := os.Open(dodoAppFlags.envConfig)
+	if err != nil {
+		return err
+	}
+	defer envConfig.Close()
+	var env installer.EnvConfig
+	if err := json.NewDecoder(envConfig).Decode(&env); err != nil {
+		return err
+	}
+	sshKey, err := os.ReadFile(dodoAppFlags.sshKey)
+	if err != nil {
+		return err
+	}
+	softClient, err := soft.NewClient(dodoAppFlags.repoAddr, sshKey, log.Default())
+	if err != nil {
+		return err
+	}
+	if err := softClient.AddRepository("app"); err == nil {
+		repo, err := softClient.GetRepo("app")
+		if err != nil {
+			return err
+		}
+		if err := initRepo(repo); err != nil {
+			return err
+		}
+		if err := welcome.UpdateDodoApp(softClient, dodoAppFlags.namespace, string(sshKey), &env); err != nil {
+			return err
+		}
+		if err := softClient.AddWebhook("app", fmt.Sprintf("http://%s/update", dodoAppFlags.self), "--active=true", "--events=push", "--content-type=json"); err != nil {
+			return err
+		}
+	} else if !errors.Is(err, soft.ErrorAlreadyExists) {
+		return err
+	}
+	s := welcome.NewDodoAppServer(dodoAppFlags.port, string(sshKey), softClient, dodoAppFlags.namespace, env)
+	return s.Start()
+}
+
+const goMod = `module dodo.app
+
+go 1.18
+`
+
+const mainGo = `package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+)
+
+var port = flag.Int("port", 8080, "Port to listen on")
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintln(w, "Hello from Dodo App!")
+}
+
+func main() {
+	flag.Parse()
+	http.HandleFunc("/", handler)
+	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
+}
+`
+
+const appCue = `app: {
+	type: "golang:1.22.0"
+	run: "main.go"
+	ingress: {
+		network: "Private" // or Public
+		subdomain: "testapp"
+		auth: enabled: false
+	}
+}
+`
+
+func initRepo(repo soft.RepoIO) error {
+	return repo.Do(func(fs soft.RepoFS) (string, error) {
+		{
+			w, err := fs.Writer("go.mod")
+			if err != nil {
+				return "", err
+			}
+			defer w.Close()
+			fmt.Fprint(w, goMod)
+		}
+		{
+			w, err := fs.Writer("main.go")
+			if err != nil {
+				return "", err
+			}
+			defer w.Close()
+			fmt.Fprintf(w, "%s", mainGo)
+		}
+		{
+			w, err := fs.Writer("app.cue")
+			if err != nil {
+				return "", err
+			}
+			defer w.Close()
+			fmt.Fprint(w, appCue)
+		}
+		return "go web app template", nil
+	})
+}
diff --git a/core/installer/cmd/main.go b/core/installer/cmd/main.go
index 5b05381..568efae 100644
--- a/core/installer/cmd/main.go
+++ b/core/installer/cmd/main.go
@@ -28,6 +28,7 @@
 	rootCmd.AddCommand(welcomeCmd())
 	rootCmd.AddCommand(rewriteCmd())
 	rootCmd.AddCommand(launcherCmd())
+	rootCmd.AddCommand(dodoAppCmd())
 }
 
 func main() {