blob: 2f9f17a05791ffd8cc0b357e7e064f76e94a4598 [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
33 r installer.AppRepository[installer.StoreApp]
34 reconciler tasks.Reconciler
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040035}
36
37func NewAppManagerServer(
38 port int,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040039 m *installer.AppManager,
40 r installer.AppRepository[installer.StoreApp],
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040041 reconciler tasks.Reconciler,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040042) *AppManagerServer {
43 return &AppManagerServer{
44 port,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040045 m,
46 r,
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040047 reconciler,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040048 }
49}
50
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040051func (s *AppManagerServer) Start() error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040052 e := echo.New()
53 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
54 e.GET("/api/app-repo", s.handleAppRepo)
55 e.POST("/api/app/:slug/render", s.handleAppRender)
56 e.POST("/api/app/:slug/install", s.handleAppInstall)
57 e.GET("/api/app/:slug", s.handleApp)
58 e.GET("/api/instance/:slug", s.handleInstance)
59 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
60 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
61 e.GET("/", s.handleIndex)
62 e.GET("/app/:slug", s.handleAppUI)
63 e.GET("/instance/:slug", s.handleInstanceUI)
64 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040065 return e.Start(fmt.Sprintf(":%d", s.port))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040066}
67
68type app struct {
69 Name string `json:"name"`
70 Icon template.HTML `json:"icon"`
71 ShortDescription string `json:"shortDescription"`
72 Slug string `json:"slug"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040073 Instances []installer.AppConfig `json:"instances,omitempty"`
74}
75
76func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
77 all, err := s.r.GetAll()
78 if err != nil {
79 return err
80 }
81 resp := make([]app, len(all))
82 for i, a := range all {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040083 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040084 }
85 return c.JSON(http.StatusOK, resp)
86}
87
88func (s *AppManagerServer) handleApp(c echo.Context) error {
89 slug := c.Param("slug")
90 a, err := s.r.Find(slug)
91 if err != nil {
92 return err
93 }
94 instances, err := s.m.FindAllInstances(slug)
95 if err != nil {
96 return err
97 }
98 for _, instance := range instances {
99 values, ok := instance.Config["Values"].(map[string]any)
100 if !ok {
101 return fmt.Errorf("Expected map")
102 }
103 for k, v := range values {
104 if k == "Network" {
105 n, ok := v.(map[string]any)
106 if !ok {
107 return fmt.Errorf("Expected map")
108 }
109 values["Network"], ok = n["Name"]
110 if !ok {
111 return fmt.Errorf("Missing Name")
112 }
113 break
114 }
115 }
116 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400117 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, instances})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400118}
119
120func (s *AppManagerServer) handleInstance(c echo.Context) error {
121 slug := c.Param("slug")
122 instance, err := s.m.FindInstance(slug)
123 if err != nil {
124 return err
125 }
126 values, ok := instance.Config["Values"].(map[string]any)
127 if !ok {
128 return fmt.Errorf("Expected map")
129 }
130 for k, v := range values {
131 if k == "Network" {
132 n, ok := v.(map[string]any)
133 if !ok {
134 return fmt.Errorf("Expected map")
135 }
136 values["Network"], ok = n["Name"]
137 if !ok {
138 return fmt.Errorf("Missing Name")
139 }
140 break
141 }
142 }
143 a, err := s.r.Find(instance.AppId)
144 if err != nil {
145 return err
146 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400147 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, []installer.AppConfig{instance}})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400148}
149
150type file struct {
151 Name string `json:"name"`
152 Contents string `json:"contents"`
153}
154
155type rendered struct {
156 Readme string `json:"readme"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400157}
158
159func (s *AppManagerServer) handleAppRender(c echo.Context) error {
160 slug := c.Param("slug")
161 contents, err := ioutil.ReadAll(c.Request().Body)
162 if err != nil {
163 return err
164 }
165 global, err := s.m.Config()
166 if err != nil {
167 return err
168 }
169 var values map[string]any
170 if err := json.Unmarshal(contents, &values); err != nil {
171 return err
172 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400173 if network, ok := values["network"]; ok {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400174 for _, n := range installer.CreateNetworks(global) {
175 if n.Name == network { // TODO(giolekva): handle not found
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400176 values["network"] = n
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400177 }
178 }
179 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400180 all := installer.Derived{
181 Global: global.Values,
182 Values: values,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400183 }
184 a, err := s.r.Find(slug)
185 if err != nil {
186 return err
187 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400188 r, err := a.Render(all)
189 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400190 return err
191 }
192 var resp rendered
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400193 resp.Readme = r.Readme
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400194 out, err := json.Marshal(resp)
195 if err != nil {
196 return err
197 }
198 if _, err := c.Response().Writer.Write(out); err != nil {
199 return err
200 }
201 return nil
202}
203
204func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
205 slug := c.Param("slug")
206 contents, err := ioutil.ReadAll(c.Request().Body)
207 if err != nil {
208 return err
209 }
210 var values map[string]any
211 if err := json.Unmarshal(contents, &values); err != nil {
212 return err
213 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400214 log.Printf("Values: %+v\n", values)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400215 a, err := s.r.Find(slug)
216 if err != nil {
217 return err
218 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400219 log.Printf("Found application: %s\n", slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400220 config, err := s.m.Config()
221 if err != nil {
222 return err
223 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400224 log.Printf("Configuration: %+v\n", config)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400225 nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
226 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
227 if err := s.m.Install(a.App, nsGen, suffixGen, values); err != nil {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400228 log.Printf("%s\n", err.Error())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229 return err
230 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400231 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
232 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400233 return c.String(http.StatusOK, "Installed")
234}
235
236func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
237 slug := c.Param("slug")
238 appConfig, err := s.m.AppConfig(slug)
239 if err != nil {
240 return err
241 }
242 contents, err := ioutil.ReadAll(c.Request().Body)
243 if err != nil {
244 return err
245 }
246 var values map[string]any
247 if err := json.Unmarshal(contents, &values); err != nil {
248 return err
249 }
250 a, err := s.r.Find(appConfig.AppId)
251 if err != nil {
252 return err
253 }
254 if err := s.m.Update(a.App, slug, values); err != nil {
255 return err
256 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400257 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
258 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400259 return c.String(http.StatusOK, "Installed")
260}
261
262func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
263 slug := c.Param("slug")
264 if err := s.m.Remove(slug); err != nil {
265 return err
266 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400267 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
268 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400269 return c.String(http.StatusOK, "Installed")
270}
271
272func (s *AppManagerServer) handleIndex(c echo.Context) error {
273 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
274 if err != nil {
275 return err
276 }
277 all, err := s.r.GetAll()
278 if err != nil {
279 return err
280 }
281 resp := make([]app, len(all))
282 for i, a := range all {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400283 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400284 }
285 return tmpl.Execute(c.Response(), resp)
286}
287
288type appContext[T any] struct {
289 App *T
290 Instance *installer.AppConfig
291 Instances []installer.AppConfig
292 AvailableNetworks []installer.Network
293}
294
295func (s *AppManagerServer) handleAppUI(c echo.Context) error {
296 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
297 if err != nil {
298 return err
299 }
300 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
301 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400302 return err
303 }
304 global, err := s.m.Config()
305 if err != nil {
306 return err
307 }
308 slug := c.Param("slug")
309 a, err := s.r.Find(slug)
310 if err != nil {
311 return err
312 }
313 instances, err := s.m.FindAllInstances(slug)
314 if err != nil {
315 return err
316 }
317 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
318 App: a,
319 Instances: instances,
320 AvailableNetworks: installer.CreateNetworks(global),
321 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400322 return err
323}
324
325func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
326 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
327 if err != nil {
328 return err
329 }
330 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
331 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
332 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400333 return err
334 }
335 global, err := s.m.Config()
336 if err != nil {
337 return err
338 }
339 slug := c.Param("slug")
340 instance, err := s.m.FindInstance(slug)
341 if err != nil {
342 return err
343 }
344 a, err := s.r.Find(instance.AppId)
345 if err != nil {
346 return err
347 }
348 instances, err := s.m.FindAllInstances(a.Name)
349 if err != nil {
350 return err
351 }
352 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
353 App: a,
354 Instance: &instance,
355 Instances: instances,
356 AvailableNetworks: installer.CreateNetworks(global),
357 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400358 return err
359}
360
361func newTemplate() *template.Template {
362 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
363}