blob: e7322ab4ba4bd72e1254c94a91ee5b327d6fc851 [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
21//go:embed appmanager-tmpl
22var mgrTmpl embed.FS
23
24//go:embed appmanager-tmpl/base.html
25var baseHtmlTmpl string
26
27//go:embed appmanager-tmpl/app.html
28var appHtmlTmpl string
29
30type AppManagerServer struct {
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040031 port int
32 m *installer.AppManager
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040033 r installer.AppRepository
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040034 reconciler tasks.Reconciler
gio778577f2024-04-29 09:44:38 +040035 h installer.HelmReleaseMonitor
36 tasks map[string]tasks.Task
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040037}
38
39func NewAppManagerServer(
40 port int,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040041 m *installer.AppManager,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040042 r installer.AppRepository,
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040043 reconciler tasks.Reconciler,
gio778577f2024-04-29 09:44:38 +040044 h installer.HelmReleaseMonitor,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040045) *AppManagerServer {
46 return &AppManagerServer{
47 port,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040048 m,
49 r,
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040050 reconciler,
gio778577f2024-04-29 09:44:38 +040051 h,
52 map[string]tasks.Task{},
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040053 }
54}
55
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040056func (s *AppManagerServer) Start() error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040057 e := echo.New()
58 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
59 e.GET("/api/app-repo", s.handleAppRepo)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040060 e.POST("/api/app/:slug/install", s.handleAppInstall)
61 e.GET("/api/app/:slug", s.handleApp)
62 e.GET("/api/instance/:slug", s.handleInstance)
63 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
64 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
65 e.GET("/", s.handleIndex)
66 e.GET("/app/:slug", s.handleAppUI)
67 e.GET("/instance/:slug", s.handleInstanceUI)
68 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040069 return e.Start(fmt.Sprintf(":%d", s.port))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040070}
71
72type app struct {
gio3cdee592024-04-17 10:15:56 +040073 Name string `json:"name"`
74 Icon template.HTML `json:"icon"`
75 ShortDescription string `json:"shortDescription"`
76 Slug string `json:"slug"`
77 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040078}
79
80func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
81 all, err := s.r.GetAll()
82 if err != nil {
83 return err
84 }
85 resp := make([]app, len(all))
86 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +040087 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040088 }
89 return c.JSON(http.StatusOK, resp)
90}
91
92func (s *AppManagerServer) handleApp(c echo.Context) error {
93 slug := c.Param("slug")
94 a, err := s.r.Find(slug)
95 if err != nil {
96 return err
97 }
gio308105e2024-04-19 13:12:13 +040098 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040099 if err != nil {
100 return err
101 }
gio44f621b2024-04-29 09:44:38 +0400102 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400103}
104
105func (s *AppManagerServer) handleInstance(c echo.Context) error {
106 slug := c.Param("slug")
107 instance, err := s.m.FindInstance(slug)
108 if err != nil {
109 return err
110 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400111 a, err := s.r.Find(instance.AppId)
112 if err != nil {
113 return err
114 }
gio778577f2024-04-29 09:44:38 +0400115 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Slug(), []installer.AppInstanceConfig{*instance}})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400116}
117
118func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
119 slug := c.Param("slug")
120 contents, err := ioutil.ReadAll(c.Request().Body)
121 if err != nil {
122 return err
123 }
124 var values map[string]any
125 if err := json.Unmarshal(contents, &values); err != nil {
126 return err
127 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400128 log.Printf("Values: %+v\n", values)
gio3cdee592024-04-17 10:15:56 +0400129 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400130 if err != nil {
131 return err
132 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400133 log.Printf("Found application: %s\n", slug)
gio3cdee592024-04-17 10:15:56 +0400134 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400135 if err != nil {
136 return err
137 }
gio3cdee592024-04-17 10:15:56 +0400138 log.Printf("Configuration: %+v\n", env)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400139 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400140 suffix, err := suffixGen.Generate()
141 if err != nil {
142 return err
143 }
gio44f621b2024-04-29 09:44:38 +0400144 instanceId := a.Slug() + suffix
gio3cdee592024-04-17 10:15:56 +0400145 appDir := fmt.Sprintf("/apps/%s", instanceId)
146 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
gio778577f2024-04-29 09:44:38 +0400147 rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
148 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400149 return err
150 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400151 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
152 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400153 if _, ok := s.tasks[instanceId]; ok {
154 panic("MUST NOT REACH!")
155 }
156 t := tasks.NewMonitorRelease(s.h, rr)
157 t.OnDone(func(err error) {
158 delete(s.tasks, instanceId)
159 })
160 s.tasks[instanceId] = t
161 go t.Start()
162 return c.String(http.StatusOK, fmt.Sprintf("/instance/%s", instanceId))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400163}
164
165func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
166 slug := c.Param("slug")
167 appConfig, err := s.m.AppConfig(slug)
168 if err != nil {
169 return err
170 }
171 contents, err := ioutil.ReadAll(c.Request().Body)
172 if err != nil {
173 return err
174 }
175 var values map[string]any
176 if err := json.Unmarshal(contents, &values); err != nil {
177 return err
178 }
gio3cdee592024-04-17 10:15:56 +0400179 a, err := installer.FindEnvApp(s.r, appConfig.AppId)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400180 if err != nil {
181 return err
182 }
gio778577f2024-04-29 09:44:38 +0400183 if _, ok := s.tasks[slug]; ok {
184 return fmt.Errorf("Update already in progress")
185 }
186 rr, err := s.m.Update(a, slug, values)
187 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400188 return err
189 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400190 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
191 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400192 t := tasks.NewMonitorRelease(s.h, rr)
193 t.OnDone(func(err error) {
194 delete(s.tasks, slug)
195 })
196 s.tasks[slug] = t
197 go t.Start()
198 return c.String(http.StatusOK, fmt.Sprintf("/instance/%s", slug))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400199}
200
201func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
202 slug := c.Param("slug")
203 if err := s.m.Remove(slug); err != nil {
204 return err
205 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400206 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
207 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400208 return c.String(http.StatusOK, "/")
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400209}
210
211func (s *AppManagerServer) handleIndex(c echo.Context) error {
212 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
213 if err != nil {
214 return err
215 }
216 all, err := s.r.GetAll()
217 if err != nil {
218 return err
219 }
220 resp := make([]app, len(all))
221 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +0400222 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400223 }
224 return tmpl.Execute(c.Response(), resp)
225}
226
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400227type appContext struct {
gio3cdee592024-04-17 10:15:56 +0400228 App installer.EnvApp
229 Instance *installer.AppInstanceConfig
230 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400231 AvailableNetworks []installer.Network
gio778577f2024-04-29 09:44:38 +0400232 Task tasks.Task
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400233}
234
235func (s *AppManagerServer) handleAppUI(c echo.Context) error {
236 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
237 if err != nil {
238 return err
239 }
240 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
241 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400242 return err
243 }
244 global, err := s.m.Config()
245 if err != nil {
246 return err
247 }
248 slug := c.Param("slug")
gio3cdee592024-04-17 10:15:56 +0400249 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400250 if err != nil {
251 return err
252 }
gio308105e2024-04-19 13:12:13 +0400253 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400254 if err != nil {
255 return err
256 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400257 err = appTmpl.Execute(c.Response(), appContext{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400258 App: a,
259 Instances: instances,
260 AvailableNetworks: installer.CreateNetworks(global),
261 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400262 return err
263}
264
265func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
266 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
267 if err != nil {
268 return err
269 }
270 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
271 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
272 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400273 return err
274 }
275 global, err := s.m.Config()
276 if err != nil {
277 return err
278 }
279 slug := c.Param("slug")
280 instance, err := s.m.FindInstance(slug)
281 if err != nil {
282 return err
283 }
gio3cdee592024-04-17 10:15:56 +0400284 a, err := installer.FindEnvApp(s.r, instance.AppId)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400285 if err != nil {
286 return err
287 }
gio44f621b2024-04-29 09:44:38 +0400288 instances, err := s.m.FindAllAppInstances(a.Slug())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400289 if err != nil {
290 return err
291 }
gio778577f2024-04-29 09:44:38 +0400292 t := s.tasks[slug]
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400293 err = appTmpl.Execute(c.Response(), appContext{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400294 App: a,
gio778577f2024-04-29 09:44:38 +0400295 Instance: instance,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400296 Instances: instances,
297 AvailableNetworks: installer.CreateNetworks(global),
gio778577f2024-04-29 09:44:38 +0400298 Task: t,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400299 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400300 return err
301}
302
303func newTemplate() *template.Template {
304 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
305}