blob: 8622fdc8ef944a9f7da87c3ab82f5c7b7b6f9693 [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"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400158}
159
160func (s *AppManagerServer) handleAppRender(c echo.Context) error {
161 slug := c.Param("slug")
162 contents, err := ioutil.ReadAll(c.Request().Body)
163 if err != nil {
164 return err
165 }
166 global, err := s.m.Config()
167 if err != nil {
168 return err
169 }
170 var values map[string]any
171 if err := json.Unmarshal(contents, &values); err != nil {
172 return err
173 }
174 if network, ok := values["Network"]; ok {
175 for _, n := range installer.CreateNetworks(global) {
176 if n.Name == network { // TODO(giolekva): handle not found
177 values["Network"] = n
178 }
179 }
180 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400181 all := installer.Derived{
182 Global: global.Values,
183 Values: values,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400184 }
185 a, err := s.r.Find(slug)
186 if err != nil {
187 return err
188 }
189 var readme bytes.Buffer
190 if err := a.Readme.Execute(&readme, all); err != nil {
191 return err
192 }
193 var resp rendered
194 resp.Readme = readme.String()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400195 out, err := json.Marshal(resp)
196 if err != nil {
197 return err
198 }
199 if _, err := c.Response().Writer.Write(out); err != nil {
200 return err
201 }
202 return nil
203}
204
205func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
206 slug := c.Param("slug")
207 contents, err := ioutil.ReadAll(c.Request().Body)
208 if err != nil {
209 return err
210 }
211 var values map[string]any
212 if err := json.Unmarshal(contents, &values); err != nil {
213 return err
214 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400215 log.Printf("Values: %+v\n", values)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400216 a, err := s.r.Find(slug)
217 if err != nil {
218 return err
219 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400220 log.Printf("Found application: %s\n", slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400221 config, err := s.m.Config()
222 if err != nil {
223 return err
224 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400225 log.Printf("Configuration: %+v\n", config)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400226 nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
227 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
228 if err := s.m.Install(a.App, nsGen, suffixGen, values); err != nil {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400229 log.Printf("%s\n", err.Error())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400230 return err
231 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400232 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
233 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400234 return c.String(http.StatusOK, "Installed")
235}
236
237func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
238 slug := c.Param("slug")
239 appConfig, err := s.m.AppConfig(slug)
240 if err != nil {
241 return err
242 }
243 contents, err := ioutil.ReadAll(c.Request().Body)
244 if err != nil {
245 return err
246 }
247 var values map[string]any
248 if err := json.Unmarshal(contents, &values); err != nil {
249 return err
250 }
251 a, err := s.r.Find(appConfig.AppId)
252 if err != nil {
253 return err
254 }
255 if err := s.m.Update(a.App, slug, values); err != nil {
256 return err
257 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400258 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
259 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400260 return c.String(http.StatusOK, "Installed")
261}
262
263func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
264 slug := c.Param("slug")
265 if err := s.m.Remove(slug); err != nil {
266 return err
267 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400268 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
269 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400270 return c.String(http.StatusOK, "Installed")
271}
272
273func (s *AppManagerServer) handleIndex(c echo.Context) error {
274 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
275 if err != nil {
276 return err
277 }
278 all, err := s.r.GetAll()
279 if err != nil {
280 return err
281 }
282 resp := make([]app, len(all))
283 for i, a := range all {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400284 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400285 }
286 return tmpl.Execute(c.Response(), resp)
287}
288
289type appContext[T any] struct {
290 App *T
291 Instance *installer.AppConfig
292 Instances []installer.AppConfig
293 AvailableNetworks []installer.Network
294}
295
296func (s *AppManagerServer) handleAppUI(c echo.Context) error {
297 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
298 if err != nil {
299 return err
300 }
301 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
302 if err != nil {
303 fmt.Println(err)
304 return err
305 }
306 global, err := s.m.Config()
307 if err != nil {
308 return err
309 }
310 slug := c.Param("slug")
311 a, err := s.r.Find(slug)
312 if err != nil {
313 return err
314 }
315 instances, err := s.m.FindAllInstances(slug)
316 if err != nil {
317 return err
318 }
319 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
320 App: a,
321 Instances: instances,
322 AvailableNetworks: installer.CreateNetworks(global),
323 })
324 fmt.Println(err)
325 return err
326}
327
328func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
329 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
330 if err != nil {
331 return err
332 }
333 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
334 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
335 if err != nil {
336 fmt.Println(err)
337 return err
338 }
339 global, err := s.m.Config()
340 if err != nil {
341 return err
342 }
343 slug := c.Param("slug")
344 instance, err := s.m.FindInstance(slug)
345 if err != nil {
346 return err
347 }
348 a, err := s.r.Find(instance.AppId)
349 if err != nil {
350 return err
351 }
352 instances, err := s.m.FindAllInstances(a.Name)
353 if err != nil {
354 return err
355 }
356 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
357 App: a,
358 Instance: &instance,
359 Instances: instances,
360 AvailableNetworks: installer.CreateNetworks(global),
361 })
362 fmt.Println(err)
363 return err
364}
365
366func newTemplate() *template.Template {
367 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
368}