blob: e8c929da16a802ad037fafbd11339ab0bc31439c [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
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,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040040 r installer.AppRepository,
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 {
gio3cdee592024-04-17 10:15:56 +040069 Name string `json:"name"`
70 Icon template.HTML `json:"icon"`
71 ShortDescription string `json:"shortDescription"`
72 Slug string `json:"slug"`
73 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040074}
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 Lekveishvili08af67a2024-01-18 08:53:05 +040083 resp[i] = app{a.Name(), a.Icon(), a.Description(), 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 }
gio308105e2024-04-19 13:12:13 +040094 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040095 if err != nil {
96 return err
97 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040098 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Name(), instances})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040099}
100
101func (s *AppManagerServer) handleInstance(c echo.Context) error {
102 slug := c.Param("slug")
103 instance, err := s.m.FindInstance(slug)
104 if err != nil {
105 return err
106 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400107 a, err := s.r.Find(instance.AppId)
108 if err != nil {
109 return err
110 }
gio3cdee592024-04-17 10:15:56 +0400111 return c.JSON(http.StatusOK, app{a.Name(), a.Icon(), a.Description(), a.Name(), []installer.AppInstanceConfig{instance}})
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400112}
113
114type file struct {
115 Name string `json:"name"`
116 Contents string `json:"contents"`
117}
118
119type rendered struct {
120 Readme string `json:"readme"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400121}
122
123func (s *AppManagerServer) handleAppRender(c echo.Context) error {
124 slug := c.Param("slug")
125 contents, err := ioutil.ReadAll(c.Request().Body)
126 if err != nil {
127 return err
128 }
gio3cdee592024-04-17 10:15:56 +0400129 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400130 if err != nil {
131 return err
132 }
133 var values map[string]any
134 if err := json.Unmarshal(contents, &values); err != nil {
135 return err
136 }
gio3cdee592024-04-17 10:15:56 +0400137 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400138 if err != nil {
139 return err
140 }
gio3cdee592024-04-17 10:15:56 +0400141 r, err := a.Render(installer.Release{}, env, values)
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400142 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400143 return err
144 }
145 var resp rendered
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400146 resp.Readme = r.Readme
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400147 out, err := json.Marshal(resp)
148 if err != nil {
149 return err
150 }
151 if _, err := c.Response().Writer.Write(out); err != nil {
152 return err
153 }
154 return nil
155}
156
157func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
158 slug := c.Param("slug")
159 contents, err := ioutil.ReadAll(c.Request().Body)
160 if err != nil {
161 return err
162 }
163 var values map[string]any
164 if err := json.Unmarshal(contents, &values); err != nil {
165 return err
166 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400167 log.Printf("Values: %+v\n", values)
gio3cdee592024-04-17 10:15:56 +0400168 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400169 if err != nil {
170 return err
171 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400172 log.Printf("Found application: %s\n", slug)
gio3cdee592024-04-17 10:15:56 +0400173 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400174 if err != nil {
175 return err
176 }
gio3cdee592024-04-17 10:15:56 +0400177 log.Printf("Configuration: %+v\n", env)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400178 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400179 suffix, err := suffixGen.Generate()
180 if err != nil {
181 return err
182 }
gio3cdee592024-04-17 10:15:56 +0400183 instanceId := a.Name() + suffix
184 appDir := fmt.Sprintf("/apps/%s", instanceId)
185 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
186 if err := s.m.Install(a, instanceId, appDir, namespace, values); err != nil {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400187 log.Printf("%s\n", err.Error())
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)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400192 return c.String(http.StatusOK, "Installed")
193}
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 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400213 if err := s.m.Update(a, slug, values); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400214 fmt.Println(err)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400215 return err
216 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400217 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
218 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400219 return c.String(http.StatusOK, "Installed")
220}
221
222func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
223 slug := c.Param("slug")
224 if err := s.m.Remove(slug); err != nil {
225 return err
226 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400227 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
228 go s.reconciler.Reconcile(ctx)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229 return c.String(http.StatusOK, "Installed")
230}
231
232func (s *AppManagerServer) handleIndex(c echo.Context) error {
233 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
234 if err != nil {
235 return err
236 }
237 all, err := s.r.GetAll()
238 if err != nil {
239 return err
240 }
241 resp := make([]app, len(all))
242 for i, a := range all {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400243 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Name(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400244 }
245 return tmpl.Execute(c.Response(), resp)
246}
247
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400248type appContext struct {
gio3cdee592024-04-17 10:15:56 +0400249 App installer.EnvApp
250 Instance *installer.AppInstanceConfig
251 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400252 AvailableNetworks []installer.Network
253}
254
255func (s *AppManagerServer) handleAppUI(c echo.Context) error {
256 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
257 if err != nil {
258 return err
259 }
260 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
261 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400262 return err
263 }
264 global, err := s.m.Config()
265 if err != nil {
266 return err
267 }
268 slug := c.Param("slug")
gio3cdee592024-04-17 10:15:56 +0400269 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400270 if err != nil {
271 return err
272 }
gio308105e2024-04-19 13:12:13 +0400273 instances, err := s.m.FindAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400274 if err != nil {
275 return err
276 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400277 err = appTmpl.Execute(c.Response(), appContext{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400278 App: a,
279 Instances: instances,
280 AvailableNetworks: installer.CreateNetworks(global),
281 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400282 return err
283}
284
285func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
286 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
287 if err != nil {
288 return err
289 }
290 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
291 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
292 if err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400293 return err
294 }
295 global, err := s.m.Config()
296 if err != nil {
297 return err
298 }
299 slug := c.Param("slug")
300 instance, err := s.m.FindInstance(slug)
301 if err != nil {
302 return err
303 }
gio3cdee592024-04-17 10:15:56 +0400304 a, err := installer.FindEnvApp(s.r, instance.AppId)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400305 if err != nil {
306 return err
307 }
gio308105e2024-04-19 13:12:13 +0400308 instances, err := s.m.FindAllAppInstances(a.Name())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400309 if err != nil {
310 return err
311 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400312 err = appTmpl.Execute(c.Response(), appContext{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400313 App: a,
314 Instance: &instance,
315 Instances: instances,
316 AvailableNetworks: installer.CreateNetworks(global),
317 })
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400318 return err
319}
320
321func newTemplate() *template.Template {
322 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
323}