blob: cff270e10d69967ac4447872fe10dd5043b702e0 [file] [log] [blame]
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04001package main
2
3import (
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +04004 "bytes"
5 "encoding/json"
6 "fmt"
7 "io/ioutil"
8 "log"
Giorgi Lekveishvili8fe056b2023-06-23 12:01:43 +04009 "net"
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040010 "net/http"
11 "net/http/httputil"
12 "net/url"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040013 "os"
14
Giorgi Lekveishvili8fe056b2023-06-23 12:01:43 +040015 "github.com/go-git/go-billy/v5/memfs"
16 "github.com/go-git/go-git/v5"
17 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
18 "github.com/go-git/go-git/v5/storage/memory"
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040019 "github.com/labstack/echo/v4"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040020 "github.com/spf13/cobra"
21 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili8fe056b2023-06-23 12:01:43 +040022
23 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040024)
25
26var appManagerFlags struct {
27 sshKey string
28 repoAddr string
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040029 port int
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040030}
31
32func appManagerCmd() *cobra.Command {
33 cmd := &cobra.Command{
34 Use: "appmanager",
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040035 RunE: appManagerCmdRun,
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040036 }
37 cmd.Flags().StringVar(
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040038 &appManagerFlags.sshKey,
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040039 "ssh-key",
40 "",
41 "",
42 )
43 cmd.Flags().StringVar(
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040044 &appManagerFlags.repoAddr,
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040045 "repo-addr",
46 "",
47 "",
48 )
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040049 cmd.Flags().IntVar(
50 &appManagerFlags.port,
51 "port",
52 8080,
53 "",
54 )
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040055 return cmd
56}
57
58func appManagerCmdRun(cmd *cobra.Command, args []string) error {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040059 sshKey, err := os.ReadFile(appManagerFlags.sshKey)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040060 if err != nil {
61 return err
62 }
63 signer, err := ssh.ParsePrivateKey(sshKey)
64 if err != nil {
65 return err
66 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040067 repo, err := cloneRepo(appManagerFlags.repoAddr, signer)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040068 if err != nil {
69 return err
70 }
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040071 kube, err := installer.NewOutOfClusterNamespaceCreator(rootFlags.kubeConfig)
72 if err != nil {
73 return err
74 }
75 m, err := installer.NewAppManager(
76 installer.NewRepoIO(repo, signer),
77 kube,
78 )
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040079 if err != nil {
80 return err
81 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040082 r := installer.NewInMemoryAppRepository(installer.CreateAllApps())
83 s := &server{
84 port: appManagerFlags.port,
85 m: m,
86 r: r,
87 }
88 s.start()
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040089 return nil
90}
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040091
92type server struct {
93 port int
94 m *installer.AppManager
95 r installer.AppRepository
96}
97
98func (s *server) start() {
99 e := echo.New()
100 e.GET("/api/app-repo", s.handleAppRepo)
101 e.POST("/api/app/:slug/render", s.handleAppRender)
102 e.POST("/api/app/:slug/install", s.handleAppInstall)
103 e.GET("/api/app/:slug", s.handleApp)
104 webapp, err := url.Parse("http://localhost:5173")
105 if err != nil {
106 panic(err)
107 }
108 // var f ff
109 e.Any("/*", echo.WrapHandler(httputil.NewSingleHostReverseProxy(webapp)))
110 // e.Any("/*", echo.WrapHandler(&f))
111 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
112 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
113}
114
115type app struct {
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +0400116 Name string `json:"name"`
117 Slug string `json:"slug"`
118 Schema string `json:"schema"`
119 Config map[string]any `json:"config"`
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400120}
121
122func (s *server) handleAppRepo(c echo.Context) error {
123 all, err := s.r.GetAll()
124 if err != nil {
125 return err
126 }
127 resp := make([]app, len(all))
128 for i, a := range all {
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +0400129 config, _ := s.m.AppConfig(a.Name) // TODO(gio): handle error
130 resp[i] = app{a.Name, a.Name, a.Schema, config}
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400131 }
132 return c.JSON(http.StatusOK, resp)
133}
134
135func (s *server) handleApp(c echo.Context) error {
136 slug := c.Param("slug")
137 a, err := s.r.Find(slug)
138 if err != nil {
139 return err
140 }
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +0400141 config, _ := s.m.AppConfig(a.Name) // TODO(gio): handle error
142 return c.JSON(http.StatusOK, app{a.Name, a.Name, a.Schema, config})
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400143}
144
145type file struct {
146 Name string `json:"name"`
147 Contents string `json:"contents"`
148}
149
150type rendered struct {
151 Readme string `json:"readme"`
152 Files []file `json:"files"`
153}
154
155func (s *server) handleAppRender(c echo.Context) error {
156 slug := c.Param("slug")
157 contents, err := ioutil.ReadAll(c.Request().Body)
158 if err != nil {
159 return err
160 }
161 global, err := s.m.Config()
162 if err != nil {
163 return err
164 }
165 var values map[string]any
166 if err := json.Unmarshal(contents, &values); err != nil {
167 return err
168 }
169 all := map[string]any{
170 "Global": global.Values,
171 "Values": values,
172 }
173 a, err := s.r.Find(slug)
174 if err != nil {
175 return err
176 }
177 var readme bytes.Buffer
178 if err := a.Readme.Execute(&readme, all); err != nil {
179 return err
180 }
181 var resp rendered
182 resp.Readme = readme.String()
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400183 for _, tmpl := range a.Templates { // TODO(giolekva): deduplicate with Install
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400184 var f bytes.Buffer
185 if err := tmpl.Execute(&f, all); err != nil {
186 fmt.Printf("%+v\n", all)
187 fmt.Println(err.Error())
188 return err
189 } else {
190 resp.Files = append(resp.Files, file{tmpl.Name(), f.String()})
191 }
192 }
193 out, err := json.Marshal(resp)
194 if err != nil {
195 return err
196 }
197 if _, err := c.Response().Writer.Write(out); err != nil {
198 return err
199 }
200 return nil
201}
202
203func (s *server) handleAppInstall(c echo.Context) error {
204 slug := c.Param("slug")
205 contents, err := ioutil.ReadAll(c.Request().Body)
206 if err != nil {
207 return err
208 }
209 var values map[string]any
210 if err := json.Unmarshal(contents, &values); err != nil {
211 return err
212 }
213 a, err := s.r.Find(slug)
214 if err != nil {
215 return err
216 }
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400217 config, err := s.m.Config()
218 if err != nil {
219 return err
220 }
221 nsGen := installer.NewCombine(
222 installer.NewPrefixGenerator(config.Values.Id+"-"),
223 installer.NewRandomSuffixGenerator(3),
224 )
225 if err := s.m.Install(*a, nsGen, values); err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400226 return err
227 }
228 return c.String(http.StatusOK, "Installed")
229}
Giorgi Lekveishvili8fe056b2023-06-23 12:01:43 +0400230
231func cloneRepo(address string, signer ssh.Signer) (*git.Repository, error) {
232 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
233 URL: address,
234 Auth: auth(signer),
235 RemoteName: "origin",
236 InsecureSkipTLS: true,
237 })
238}
239
240func auth(signer ssh.Signer) *gitssh.PublicKeys {
241 return &gitssh.PublicKeys{
242 Signer: signer,
243 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
244 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
245 // TODO(giolekva): verify server public key
246 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
247 return nil
248 },
249 },
250 }
251}