| giolekva | d5a58c4 | 2020-05-07 22:18:35 +0400 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 4 | "context" |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 5 | "flag" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "log" |
| 9 | "net/http" |
| 10 | "os" |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 11 | "syscall" |
| giolekva | d5a58c4 | 2020-05-07 22:18:35 +0400 | [diff] [blame] | 12 | |
| giolekva | fe0765f | 2020-05-12 14:09:09 +0400 | [diff] [blame] | 13 | "github.com/golang/glog" |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 14 | "github.com/google/uuid" |
| 15 | apiv1 "k8s.io/api/core/v1" |
| 16 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 17 | "k8s.io/client-go/kubernetes" |
| 18 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" |
| 19 | "k8s.io/client-go/rest" |
| 20 | "k8s.io/client-go/tools/clientcmd" |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 21 | |
| 22 | app "github.com/giolekva/pcloud/appmanager" |
| giolekva | d5a58c4 | 2020-05-07 22:18:35 +0400 | [diff] [blame] | 23 | ) |
| 24 | |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 25 | var kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file.") |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 26 | var port = flag.Int("port", 1234, "Port to listen on.") |
| giolekva | fe0765f | 2020-05-12 14:09:09 +0400 | [diff] [blame] | 27 | var apiAddr = flag.String("api_addr", "", "PCloud API service address.") |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 28 | |
| 29 | var helmUploadPage = ` |
| 30 | <html> |
| 31 | <head> |
| 32 | <title>Upload Helm chart</title> |
| 33 | </head> |
| 34 | <body> |
| 35 | <form enctype="multipart/form-data" action="/" method="post"> |
| 36 | <input type="file" name="chartfile" /> |
| 37 | <input type="submit" value="upload" /> |
| 38 | </form> |
| 39 | </body> |
| 40 | </html> |
| 41 | ` |
| 42 | |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 43 | type handler struct { |
| 44 | nsClient corev1.NamespaceInterface |
| 45 | } |
| 46 | |
| 47 | func (hn *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 48 | if r.Method == "GET" { |
| 49 | _, err := io.WriteString(w, helmUploadPage) |
| 50 | if err != nil { |
| 51 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 52 | } |
| 53 | } else if r.Method == "POST" { |
| 54 | r.ParseMultipartForm(1000000) |
| 55 | file, handler, err := r.FormFile("chartfile") |
| 56 | if err != nil { |
| 57 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 58 | return |
| 59 | } |
| 60 | defer file.Close() |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 61 | tmp := uuid.New().String() |
| 62 | if tmp == "" { |
| 63 | http.Error(w, "Could not generate temp dir", http.StatusInternalServerError) |
| 64 | return |
| 65 | } |
| 66 | p := "/tmp/" + tmp |
| 67 | // TODO(giolekva): defer rmdir |
| 68 | if err := syscall.Mkdir(p, 0777); err != nil { |
| 69 | http.Error(w, "Could not create temp dir", http.StatusInternalServerError) |
| 70 | return |
| 71 | } |
| 72 | p += "/" + handler.Filename |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 73 | f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, 0666) |
| 74 | if err != nil { |
| 75 | fmt.Println(err) |
| 76 | return |
| 77 | } |
| 78 | defer f.Close() |
| 79 | _, err = io.Copy(f, file) |
| 80 | if err != nil { |
| 81 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 82 | return |
| 83 | } |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 84 | if err = installHelmChart(p, hn.nsClient); err != nil { |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 85 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 86 | return |
| 87 | } |
| 88 | w.Write([]byte("Installed")) |
| giolekva | d5a58c4 | 2020-05-07 22:18:35 +0400 | [diff] [blame] | 89 | } |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 90 | } |
| 91 | |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 92 | func installHelmChart(path string, nsClient corev1.NamespaceInterface) error { |
| 93 | h, err := app.HelmChartFromTar(path) |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 94 | if err != nil { |
| 95 | return err |
| 96 | } |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 97 | if err = app.InstallSchema(h.Schema, *apiAddr); err != nil { |
| giolekva | fe0765f | 2020-05-12 14:09:09 +0400 | [diff] [blame] | 98 | return err |
| 99 | } |
| 100 | glog.Infof("Installed schema: %s", h.Schema) |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 101 | namespace := fmt.Sprintf("app-%s", h.Name) |
| 102 | if err = createNamespace(nsClient, namespace); err != nil { |
| 103 | return err |
| 104 | } |
| 105 | glog.Infof("Created namespaces: %s", namespace) |
| 106 | if err = h.Install( |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 107 | "/usr/local/bin/helm", |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 108 | map[string]string{}); err != nil { |
| 109 | return err |
| 110 | } |
| 111 | glog.Info("Installed") |
| 112 | return nil |
| 113 | } |
| 114 | |
| 115 | func createNamespace(nsClient corev1.NamespaceInterface, name string) error { |
| 116 | _, err := nsClient.Create( |
| 117 | context.TODO(), |
| 118 | &apiv1.Namespace{ |
| 119 | ObjectMeta: metav1.ObjectMeta{ |
| 120 | Name: name}}, |
| 121 | metav1.CreateOptions{}) |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 122 | return err |
| 123 | } |
| 124 | |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 125 | func getKubeConfig() (*rest.Config, error) { |
| 126 | if *kubeconfig != "" { |
| 127 | return clientcmd.BuildConfigFromFlags("", *kubeconfig) |
| 128 | } else { |
| 129 | return rest.InClusterConfig() |
| 130 | } |
| 131 | } |
| 132 | |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 133 | func main() { |
| 134 | flag.Parse() |
| giolekva | ebea582 | 2020-05-12 20:52:19 +0400 | [diff] [blame^] | 135 | config, err := getKubeConfig() |
| 136 | if err != nil { |
| 137 | glog.Fatalf("Could not initialize Kubeconfig: %v", err) |
| 138 | } |
| 139 | clientset, err := kubernetes.NewForConfig(config) |
| 140 | if err != nil { |
| 141 | glog.Fatalf("Could not create Kubernetes API client: %v", err) |
| 142 | } |
| 143 | namespaces := clientset.CoreV1().Namespaces() |
| 144 | http.Handle("/", &handler{namespaces}) |
| giolekva | a4a153b | 2020-05-12 11:49:53 +0400 | [diff] [blame] | 145 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) |
| 146 | |
| giolekva | d5a58c4 | 2020-05-07 22:18:35 +0400 | [diff] [blame] | 147 | } |