appmanager: move to core/appmanager
diff --git a/core/appmanager/cmd/main.go b/core/appmanager/cmd/main.go
new file mode 100644
index 0000000..8d6695d
--- /dev/null
+++ b/core/appmanager/cmd/main.go
@@ -0,0 +1,250 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "syscall"
+
+ "github.com/golang/glog"
+ "github.com/google/uuid"
+ apiv1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+ "k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/clientcmd"
+
+ app "github.com/giolekva/pcloud/core/appmanager"
+)
+
+var kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file.")
+var helmBin = flag.String("helm_bin", "/usr/local/bin/helm", "Path to the Helm binary.")
+var port = flag.Int("port", 1234, "Port to listen on.")
+var apiAddr = flag.String("api_addr", "", "PCloud API service address.")
+var managerStoreFile = flag.String("manager_store_file", "", "Persistent file containing installed application information.")
+
+var helmUploadPage = `
+<html>
+<head>
+ <title>Upload Helm chart</title>
+</head>
+<body>
+<form enctype="multipart/form-data" method="post">
+ <input type="file" name="chartfile" />
+ <input type="submit" value="upload" />
+</form>
+</body>
+</html>
+`
+
+type handler struct {
+ client *kubernetes.Clientset
+ manager *app.Manager
+ launcher app.Launcher
+}
+
+func (hn *handler) handleInstall(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ _, err := io.WriteString(w, helmUploadPage)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ } else if r.Method == "POST" {
+ r.ParseMultipartForm(1000000)
+ file, handler, err := r.FormFile("chartfile")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer file.Close()
+ tmp := uuid.New().String()
+ if tmp == "" {
+ http.Error(w, "Could not generate temp dir", http.StatusInternalServerError)
+ return
+ }
+ p := "/tmp/" + tmp
+ // TODO(giolekva): defer rmdir
+ if err := syscall.Mkdir(p, 0777); err != nil {
+ http.Error(w, "Could not create temp dir", http.StatusInternalServerError)
+ return
+ }
+ p += "/" + handler.Filename
+ f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer f.Close()
+ _, err = io.Copy(f, file)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err = hn.installHelmChart(p); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Write([]byte("Installed"))
+ }
+}
+
+type trigger struct {
+ App string `json:"app"`
+ Action string `json:"action"`
+}
+
+func (hn *handler) handleTriggers(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ http.Error(w, "Only GET method is supported on /triggers", http.StatusBadRequest)
+ return
+ }
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ // TODO(giolekva): check if exists
+ triggerOnType := r.Form["trigger_on_type"][0]
+ triggerOnEvent := r.Form["trigger_on_event"][0]
+ var triggers []trigger
+ for _, a := range hn.manager.Apps {
+ for _, t := range a.Triggers.Triggers {
+ if t.TriggerOn.Type == triggerOnType && t.TriggerOn.Event == triggerOnEvent {
+ triggers = append(triggers, trigger{a.Name, t.Action})
+ }
+ }
+ }
+ respBody, err := json.Marshal(triggers)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if _, err := io.Copy(w, bytes.NewReader(respBody)); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+type actionReq struct {
+ App string `json:"app"`
+ Action string `json:"action"`
+ Args map[string]interface{} `json:"args"`
+}
+
+func (hn *handler) handleLaunchAction(w http.ResponseWriter, r *http.Request) {
+ actionStr, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ var req actionReq
+ if err := json.Unmarshal(actionStr, &req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ if err := hn.launchAction(req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+}
+
+func (hn *handler) launchAction(req actionReq) error {
+ for _, a := range hn.manager.Apps {
+ if a.Name != req.App {
+ continue
+ }
+ for _, action := range a.Actions.Actions {
+ if action.Name == req.Action {
+ return hn.launcher.Launch(a.Namespace, action.Template, req.Args)
+ }
+ }
+ }
+ return fmt.Errorf("Action not found: %s %s", req.App, req.Action)
+}
+
+func (hn *handler) installHelmChart(path string) error {
+ h, err := app.HelmChartFromTar(path)
+ if err != nil {
+ return err
+ }
+ if err := h.Render(
+ *helmBin,
+ map[string]string{}); err != nil {
+ return err
+ }
+ glog.Info("Rendered templates")
+ if err = app.InstallSchema(h.Schema, *apiAddr); err != nil {
+ return err
+ }
+ glog.Infof("Installed schema: %s", h.Schema)
+ err = createNamespace(hn.client.CoreV1().Namespaces(), h.Namespace)
+ if err != nil {
+ return err
+ }
+ glog.Infof("Created namespaces: %s", h.Namespace)
+ if h.Type == "application" {
+ if err := h.Install(*helmBin); err != nil {
+ return err
+ }
+ glog.Info("Deployed")
+ } else {
+ glog.Info("Skipping deployment as we got library chart.")
+ }
+ hn.manager.Apps[h.Name] = app.App{h.Name, h.Namespace, h.Triggers, h.Actions}
+ app.StoreManagerStateToFile(hn.manager, *managerStoreFile)
+ for _, a := range h.Init.PostInstall.CallAction {
+ if err := hn.launchAction(actionReq{a.App, a.Action, a.Args}); err != nil {
+ return err
+ }
+ }
+ glog.Info("Installed")
+ return nil
+}
+
+func createNamespace(nsClient corev1.NamespaceInterface, name string) error {
+ _, err := nsClient.Create(
+ context.TODO(),
+ &apiv1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name}},
+ metav1.CreateOptions{})
+ return err
+}
+
+func getKubeConfig() (*rest.Config, error) {
+ if *kubeconfig != "" {
+ return clientcmd.BuildConfigFromFlags("", *kubeconfig)
+ } else {
+ return rest.InClusterConfig()
+ }
+}
+
+func main() {
+ flag.Parse()
+ config, err := getKubeConfig()
+ if err != nil {
+ glog.Fatalf("Could not initialize Kubeconfig: %v", err)
+ }
+ clientset, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ glog.Fatalf("Could not create Kubernetes API client: %v", err)
+ }
+ manager, err := app.LoadManagerStateFromFile(*managerStoreFile)
+ if err != nil {
+ glog.Fatalf("Could ot initialize manager: %v", err)
+ }
+ glog.Info(manager)
+ h := handler{clientset, manager, app.NewK8sLauncher(clientset)}
+ http.HandleFunc("/triggers", h.handleTriggers)
+ http.HandleFunc("/launch_action", h.handleLaunchAction)
+ http.HandleFunc("/", h.handleInstall)
+ log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
+
+}