blob: 052042be183435689499baf25bb46e15757984da [file] [log] [blame]
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04001package welcome
2
3import (
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +04004 "context"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04005 "embed"
6 "encoding/json"
7 "fmt"
8 "html/template"
9 "io/ioutil"
10 "log"
11 "net/http"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040012 "time"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040013
14 "github.com/Masterminds/sprig/v3"
15 "github.com/labstack/echo/v4"
16
17 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040018 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040019)
20
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040021//go:embed appmanager-tmpl/*
22var appTmpls embed.FS
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040023
24type AppManagerServer struct {
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040025 port int
26 m *installer.AppManager
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040027 r installer.AppRepository
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040028 reconciler tasks.Reconciler
gio778577f2024-04-29 09:44:38 +040029 h installer.HelmReleaseMonitor
30 tasks map[string]tasks.Task
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040031 tmpl tmplts
32}
33
34type tmplts struct {
35 index *template.Template
36 app *template.Template
37}
38
39func parseTemplatesAppManager(fs embed.FS) (tmplts, error) {
40 base, err := template.New("base.html").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(fs, "appmanager-tmpl/base.html")
41 if err != nil {
42 return tmplts{}, err
43 }
44 parse := func(path string) (*template.Template, error) {
45 if b, err := base.Clone(); err != nil {
46 return nil, err
47 } else {
48 return b.ParseFS(fs, path)
49 }
50 }
51 index, err := parse("appmanager-tmpl/index.html")
52 if err != nil {
53 return tmplts{}, err
54 }
55 app, err := parse("appmanager-tmpl/app.html")
56 if err != nil {
57 return tmplts{}, err
58 }
59 return tmplts{index, app}, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040060}
61
62func NewAppManagerServer(
63 port int,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040064 m *installer.AppManager,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040065 r installer.AppRepository,
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040066 reconciler tasks.Reconciler,
gio778577f2024-04-29 09:44:38 +040067 h installer.HelmReleaseMonitor,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040068) (*AppManagerServer, error) {
69 tmpl, err := parseTemplatesAppManager(appTmpls)
70 if err != nil {
71 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040072 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040073 return &AppManagerServer{
74 port: port,
75 m: m,
76 r: r,
77 reconciler: reconciler,
78 h: h,
79 tasks: make(map[string]tasks.Task),
80 tmpl: tmpl,
81 }, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040082}
83
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040084func (s *AppManagerServer) Start() error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040085 e := echo.New()
86 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
87 e.GET("/api/app-repo", s.handleAppRepo)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040088 e.POST("/api/app/:slug/install", s.handleAppInstall)
89 e.GET("/api/app/:slug", s.handleApp)
90 e.GET("/api/instance/:slug", s.handleInstance)
91 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
92 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
93 e.GET("/", s.handleIndex)
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040094 e.GET("/not-installed", s.handleNotInstalledApps)
95 e.GET("/installed", s.handleInstalledApps)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040096 e.GET("/app/:slug", s.handleAppUI)
97 e.GET("/instance/:slug", s.handleInstanceUI)
98 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040099 return e.Start(fmt.Sprintf(":%d", s.port))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400100}
101
102type app struct {
gio3cdee592024-04-17 10:15:56 +0400103 Name string `json:"name"`
104 Icon template.HTML `json:"icon"`
105 ShortDescription string `json:"shortDescription"`
106 Slug string `json:"slug"`
107 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400108}
109
110func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
111 all, err := s.r.GetAll()
112 if err != nil {
113 return err
114 }
115 resp := make([]app, len(all))
116 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +0400117 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400118 }
119 return c.JSON(http.StatusOK, resp)
120}
121
122func (s *AppManagerServer) handleApp(c echo.Context) error {
123 slug := c.Param("slug")
124 a, err := s.r.Find(slug)
125 if err != nil {
126 return err
127 }
gio308105e2024-04-19 13:12:13 +0400128 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400129 if err != nil {
130 return err
131 }
gio44f621b2024-04-29 09:44:38 +0400132 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400133}
134
135func (s *AppManagerServer) handleInstance(c echo.Context) error {
136 slug := c.Param("slug")
137 instance, err := s.m.FindInstance(slug)
138 if err != nil {
139 return err
140 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400141 a, err := s.r.Find(instance.AppId)
142 if err != nil {
143 return err
144 }
gio778577f2024-04-29 09:44:38 +0400145 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Slug(), []installer.AppInstanceConfig{*instance}})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400146}
147
148func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
149 slug := c.Param("slug")
150 contents, err := ioutil.ReadAll(c.Request().Body)
151 if err != nil {
152 return err
153 }
154 var values map[string]any
155 if err := json.Unmarshal(contents, &values); err != nil {
156 return err
157 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400158 log.Printf("Values: %+v\n", values)
gio3cdee592024-04-17 10:15:56 +0400159 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400160 if err != nil {
161 return err
162 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400163 log.Printf("Found application: %s\n", slug)
gio3cdee592024-04-17 10:15:56 +0400164 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400165 if err != nil {
166 return err
167 }
gio3cdee592024-04-17 10:15:56 +0400168 log.Printf("Configuration: %+v\n", env)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400169 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400170 suffix, err := suffixGen.Generate()
171 if err != nil {
172 return err
173 }
gio44f621b2024-04-29 09:44:38 +0400174 instanceId := a.Slug() + suffix
gio3cdee592024-04-17 10:15:56 +0400175 appDir := fmt.Sprintf("/apps/%s", instanceId)
176 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
gio778577f2024-04-29 09:44:38 +0400177 rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
178 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400179 return err
180 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400181 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
182 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400183 if _, ok := s.tasks[instanceId]; ok {
184 panic("MUST NOT REACH!")
185 }
186 t := tasks.NewMonitorRelease(s.h, rr)
187 t.OnDone(func(err error) {
188 delete(s.tasks, instanceId)
189 })
190 s.tasks[instanceId] = t
191 go t.Start()
192 return c.String(http.StatusOK, fmt.Sprintf("/instance/%s", instanceId))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400193}
194
195func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
196 slug := c.Param("slug")
197 appConfig, err := s.m.AppConfig(slug)
198 if err != nil {
199 return err
200 }
201 contents, err := ioutil.ReadAll(c.Request().Body)
202 if err != nil {
203 return err
204 }
205 var values map[string]any
206 if err := json.Unmarshal(contents, &values); err != nil {
207 return err
208 }
gio3cdee592024-04-17 10:15:56 +0400209 a, err := installer.FindEnvApp(s.r, appConfig.AppId)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400210 if err != nil {
211 return err
212 }
gio778577f2024-04-29 09:44:38 +0400213 if _, ok := s.tasks[slug]; ok {
214 return fmt.Errorf("Update already in progress")
215 }
216 rr, err := s.m.Update(a, slug, values)
217 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400218 return err
219 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400220 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
221 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400222 t := tasks.NewMonitorRelease(s.h, rr)
223 t.OnDone(func(err error) {
224 delete(s.tasks, slug)
225 })
226 s.tasks[slug] = t
227 go t.Start()
228 return c.String(http.StatusOK, fmt.Sprintf("/instance/%s", slug))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229}
230
231func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
232 slug := c.Param("slug")
233 if err := s.m.Remove(slug); err != nil {
234 return err
235 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400236 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
237 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400238 return c.String(http.StatusOK, "/")
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400239}
240
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400241type PageData struct {
242 Apps []app
243 CurrentPage string
244}
245
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400246func (s *AppManagerServer) handleIndex(c echo.Context) error {
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400247 all, err := s.r.GetAll()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400248 if err != nil {
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400249 log.Printf("all apps: %v", err)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400250 return err
251 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400252 resp := make([]app, 0)
253 for _, a := range all {
254 instances, err := s.m.FindAllAppInstances(a.Slug())
255 if err != nil {
256 return err
257 }
258 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
259 }
260 data := PageData{
261 Apps: resp,
262 CurrentPage: "ALL",
263 }
264 if err := s.tmpl.index.Execute(c.Response(), data); err != nil {
265 log.Printf("executing template: %v", err)
266 return err
267 }
268 return nil
269}
270
271func (s *AppManagerServer) handleNotInstalledApps(c echo.Context) error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400272 all, err := s.r.GetAll()
273 if err != nil {
274 return err
275 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400276 resp := make([]app, 0)
277 for _, a := range all {
278 instances, err := s.m.FindAllAppInstances(a.Slug())
279 if err != nil {
280 return err
281 }
282 if len(instances) == 0 {
283 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
284 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400285 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400286 data := PageData{
287 Apps: resp,
288 CurrentPage: "NOT_INSTALLED",
289 }
290 if err := s.tmpl.index.Execute(c.Response(), data); err != nil {
291 return err
292 }
293 return nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400294}
295
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400296func (s *AppManagerServer) handleInstalledApps(c echo.Context) error {
297 all, err := s.r.GetAll()
298 if err != nil {
299 return err
300 }
301 resp := make([]app, 0)
302 for _, a := range all {
303 instances, err := s.m.FindAllAppInstances(a.Slug())
304 if err != nil {
305 return err
306 }
307 if len(instances) != 0 {
308 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
309 }
310 }
311 data := PageData{
312 Apps: resp,
313 CurrentPage: "INSTALLED",
314 }
315 if err := s.tmpl.index.Execute(c.Response(), data); err != nil {
316 return err
317 }
318 return nil
319}
320
321type appPageData struct {
gio3cdee592024-04-17 10:15:56 +0400322 App installer.EnvApp
323 Instance *installer.AppInstanceConfig
324 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400325 AvailableNetworks []installer.Network
gio778577f2024-04-29 09:44:38 +0400326 Task tasks.Task
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400327 CurrentPage string
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400328}
329
330func (s *AppManagerServer) handleAppUI(c echo.Context) error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400331 global, err := s.m.Config()
332 if err != nil {
333 return err
334 }
335 slug := c.Param("slug")
gio3cdee592024-04-17 10:15:56 +0400336 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400337 if err != nil {
338 return err
339 }
gio308105e2024-04-19 13:12:13 +0400340 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400341 if err != nil {
342 return err
343 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400344 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400345 App: a,
346 Instances: instances,
347 AvailableNetworks: installer.CreateNetworks(global),
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400348 CurrentPage: a.Name(),
349 }
350 return s.tmpl.app.Execute(c.Response(), data)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400351}
352
353func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400354 global, err := s.m.Config()
355 if err != nil {
356 return err
357 }
358 slug := c.Param("slug")
359 instance, err := s.m.FindInstance(slug)
360 if err != nil {
361 return err
362 }
gio3cdee592024-04-17 10:15:56 +0400363 a, err := installer.FindEnvApp(s.r, instance.AppId)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400364 if err != nil {
365 return err
366 }
gio44f621b2024-04-29 09:44:38 +0400367 instances, err := s.m.FindAllAppInstances(a.Slug())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400368 if err != nil {
369 return err
370 }
gio778577f2024-04-29 09:44:38 +0400371 t := s.tasks[slug]
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400372 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400373 App: a,
gio778577f2024-04-29 09:44:38 +0400374 Instance: instance,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400375 Instances: instances,
376 AvailableNetworks: installer.CreateNetworks(global),
gio778577f2024-04-29 09:44:38 +0400377 Task: t,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400378 CurrentPage: instance.Id,
379 }
380 return s.tmpl.app.Execute(c.Response(), data)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400381}