blob: 5d7e5375f4bb3a058db4d9d8aa8b3ffc5d3f10fd [file] [log] [blame]
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04001package welcome
2
3import (
4 "bytes"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +04005 "context"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04006 "embed"
7 "encoding/json"
8 "fmt"
9 "html/template"
10 "io/ioutil"
11 "log"
12 "net/http"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040013 "time"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040014
15 "github.com/Masterminds/sprig/v3"
16 "github.com/labstack/echo/v4"
17
18 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040019 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040020)
21
22//go:embed appmanager-tmpl
23var mgrTmpl embed.FS
24
25//go:embed appmanager-tmpl/base.html
26var baseHtmlTmpl string
27
28//go:embed appmanager-tmpl/app.html
29var appHtmlTmpl string
30
31type AppManagerServer struct {
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040032 port int
33 m *installer.AppManager
34 r installer.AppRepository[installer.StoreApp]
35 reconciler tasks.Reconciler
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040036}
37
38func NewAppManagerServer(
39 port int,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040040 m *installer.AppManager,
41 r installer.AppRepository[installer.StoreApp],
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040042 reconciler tasks.Reconciler,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040043) *AppManagerServer {
44 return &AppManagerServer{
45 port,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040046 m,
47 r,
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040048 reconciler,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040049 }
50}
51
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040052func (s *AppManagerServer) Start() error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040053 e := echo.New()
54 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
55 e.GET("/api/app-repo", s.handleAppRepo)
56 e.POST("/api/app/:slug/render", s.handleAppRender)
57 e.POST("/api/app/:slug/install", s.handleAppInstall)
58 e.GET("/api/app/:slug", s.handleApp)
59 e.GET("/api/instance/:slug", s.handleInstance)
60 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
61 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
62 e.GET("/", s.handleIndex)
63 e.GET("/app/:slug", s.handleAppUI)
64 e.GET("/instance/:slug", s.handleInstanceUI)
65 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040066 return e.Start(fmt.Sprintf(":%d", s.port))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040067}
68
69type app struct {
70 Name string `json:"name"`
71 Icon template.HTML `json:"icon"`
72 ShortDescription string `json:"shortDescription"`
73 Slug string `json:"slug"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040074 Instances []installer.AppConfig `json:"instances,omitempty"`
75}
76
77func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
78 all, err := s.r.GetAll()
79 if err != nil {
80 return err
81 }
82 resp := make([]app, len(all))
83 for i, a := range all {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040084 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040085 }
86 return c.JSON(http.StatusOK, resp)
87}
88
89func (s *AppManagerServer) handleApp(c echo.Context) error {
90 slug := c.Param("slug")
91 a, err := s.r.Find(slug)
92 if err != nil {
93 return err
94 }
95 instances, err := s.m.FindAllInstances(slug)
96 if err != nil {
97 return err
98 }
99 for _, instance := range instances {
100 values, ok := instance.Config["Values"].(map[string]any)
101 if !ok {
102 return fmt.Errorf("Expected map")
103 }
104 for k, v := range values {
105 if k == "Network" {
106 n, ok := v.(map[string]any)
107 if !ok {
108 return fmt.Errorf("Expected map")
109 }
110 values["Network"], ok = n["Name"]
111 if !ok {
112 return fmt.Errorf("Missing Name")
113 }
114 break
115 }
116 }
117 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400118 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, instances})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400119}
120
121func (s *AppManagerServer) handleInstance(c echo.Context) error {
122 slug := c.Param("slug")
123 instance, err := s.m.FindInstance(slug)
124 if err != nil {
125 return err
126 }
127 values, ok := instance.Config["Values"].(map[string]any)
128 if !ok {
129 return fmt.Errorf("Expected map")
130 }
131 for k, v := range values {
132 if k == "Network" {
133 n, ok := v.(map[string]any)
134 if !ok {
135 return fmt.Errorf("Expected map")
136 }
137 values["Network"], ok = n["Name"]
138 if !ok {
139 return fmt.Errorf("Missing Name")
140 }
141 break
142 }
143 }
144 a, err := s.r.Find(instance.AppId)
145 if err != nil {
146 return err
147 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400148 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, []installer.AppConfig{instance}})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400149}
150
151type file struct {
152 Name string `json:"name"`
153 Contents string `json:"contents"`
154}
155
156type rendered struct {
157 Readme string `json:"readme"`
158 Files []file `json:"files"`
159}
160
161func (s *AppManagerServer) handleAppRender(c echo.Context) error {
162 slug := c.Param("slug")
163 contents, err := ioutil.ReadAll(c.Request().Body)
164 if err != nil {
165 return err
166 }
167 global, err := s.m.Config()
168 if err != nil {
169 return err
170 }
171 var values map[string]any
172 if err := json.Unmarshal(contents, &values); err != nil {
173 return err
174 }
175 if network, ok := values["Network"]; ok {
176 for _, n := range installer.CreateNetworks(global) {
177 if n.Name == network { // TODO(giolekva): handle not found
178 values["Network"] = n
179 }
180 }
181 }
182 all := map[string]any{
183 "Global": global.Values,
184 "Values": values,
185 }
186 a, err := s.r.Find(slug)
187 if err != nil {
188 return err
189 }
190 var readme bytes.Buffer
191 if err := a.Readme.Execute(&readme, all); err != nil {
192 return err
193 }
194 var resp rendered
195 resp.Readme = readme.String()
196 for _, tmpl := range a.Templates { // TODO(giolekva): deduplicate with Install
197 var f bytes.Buffer
198 if err := tmpl.Execute(&f, all); err != nil {
199 fmt.Printf("%+v\n", all)
200 fmt.Println(err.Error())
201 return err
202 } else {
203 resp.Files = append(resp.Files, file{tmpl.Name(), f.String()})
204 }
205 }
206 out, err := json.Marshal(resp)
207 if err != nil {
208 return err
209 }
210 if _, err := c.Response().Writer.Write(out); err != nil {
211 return err
212 }
213 return nil
214}
215
216func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
217 slug := c.Param("slug")
218 contents, err := ioutil.ReadAll(c.Request().Body)
219 if err != nil {
220 return err
221 }
222 var values map[string]any
223 if err := json.Unmarshal(contents, &values); err != nil {
224 return err
225 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400226 log.Printf("Values: %+v\n", values)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400227 a, err := s.r.Find(slug)
228 if err != nil {
229 return err
230 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400231 log.Printf("Found application: %s\n", slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400232 config, err := s.m.Config()
233 if err != nil {
234 return err
235 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400236 log.Printf("Configuration: %+v\n", config)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400237 nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
238 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
239 if err := s.m.Install(a.App, nsGen, suffixGen, values); err != nil {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400240 log.Printf("%s\n", err.Error())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400241 return err
242 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400243 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
244 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400245 return c.String(http.StatusOK, "Installed")
246}
247
248func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
249 slug := c.Param("slug")
250 appConfig, err := s.m.AppConfig(slug)
251 if err != nil {
252 return err
253 }
254 contents, err := ioutil.ReadAll(c.Request().Body)
255 if err != nil {
256 return err
257 }
258 var values map[string]any
259 if err := json.Unmarshal(contents, &values); err != nil {
260 return err
261 }
262 a, err := s.r.Find(appConfig.AppId)
263 if err != nil {
264 return err
265 }
266 if err := s.m.Update(a.App, slug, values); err != nil {
267 return err
268 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400269 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
270 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400271 return c.String(http.StatusOK, "Installed")
272}
273
274func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
275 slug := c.Param("slug")
276 if err := s.m.Remove(slug); err != nil {
277 return err
278 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400279 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
280 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400281 return c.String(http.StatusOK, "Installed")
282}
283
284func (s *AppManagerServer) handleIndex(c echo.Context) error {
285 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
286 if err != nil {
287 return err
288 }
289 all, err := s.r.GetAll()
290 if err != nil {
291 return err
292 }
293 resp := make([]app, len(all))
294 for i, a := range all {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400295 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400296 }
297 return tmpl.Execute(c.Response(), resp)
298}
299
300type appContext[T any] struct {
301 App *T
302 Instance *installer.AppConfig
303 Instances []installer.AppConfig
304 AvailableNetworks []installer.Network
305}
306
307func (s *AppManagerServer) handleAppUI(c echo.Context) error {
308 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
309 if err != nil {
310 return err
311 }
312 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
313 if err != nil {
314 fmt.Println(err)
315 return err
316 }
317 global, err := s.m.Config()
318 if err != nil {
319 return err
320 }
321 slug := c.Param("slug")
322 a, err := s.r.Find(slug)
323 if err != nil {
324 return err
325 }
326 instances, err := s.m.FindAllInstances(slug)
327 if err != nil {
328 return err
329 }
330 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
331 App: a,
332 Instances: instances,
333 AvailableNetworks: installer.CreateNetworks(global),
334 })
335 fmt.Println(err)
336 return err
337}
338
339func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
340 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
341 if err != nil {
342 return err
343 }
344 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
345 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
346 if err != nil {
347 fmt.Println(err)
348 return err
349 }
350 global, err := s.m.Config()
351 if err != nil {
352 return err
353 }
354 slug := c.Param("slug")
355 instance, err := s.m.FindInstance(slug)
356 if err != nil {
357 return err
358 }
359 a, err := s.r.Find(instance.AppId)
360 if err != nil {
361 return err
362 }
363 instances, err := s.m.FindAllInstances(a.Name)
364 if err != nil {
365 return err
366 }
367 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
368 App: a,
369 Instance: &instance,
370 Instances: instances,
371 AvailableNetworks: installer.CreateNetworks(global),
372 })
373 fmt.Println(err)
374 return err
375}
376
377func newTemplate() *template.Template {
378 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
379}