blob: a5050ef40685331cdc80dfcec0903898c74510b9 [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"
giof6ad2982024-08-23 17:42:49 +04007 "errors"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04008 "fmt"
9 "html/template"
giof6ad2982024-08-23 17:42:49 +040010 "net"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040011 "net/http"
giof6ad2982024-08-23 17:42:49 +040012 "strconv"
13 "strings"
14 "sync"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040015 "time"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040016
17 "github.com/Masterminds/sprig/v3"
gioaa0fcdb2024-06-10 22:19:25 +040018 "github.com/gorilla/mux"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040019
20 "github.com/giolekva/pcloud/core/installer"
giof6ad2982024-08-23 17:42:49 +040021 "github.com/giolekva/pcloud/core/installer/cluster"
22 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040023 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040024)
25
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040026//go:embed appmanager-tmpl/*
27var appTmpls embed.FS
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040028
giof6ad2982024-08-23 17:42:49 +040029type taskForward struct {
30 task tasks.Task
31 redirectTo string
gio8c876172024-10-05 12:25:13 +040032 id int
giof6ad2982024-08-23 17:42:49 +040033}
34
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040035type AppManagerServer struct {
giof6ad2982024-08-23 17:42:49 +040036 l sync.Locker
37 port int
38 repo soft.RepoIO
39 m *installer.AppManager
40 r installer.AppRepository
41 fr installer.AppRepository
42 reconciler *tasks.FixedReconciler
43 h installer.HelmReleaseMonitor
44 cnc installer.ClusterNetworkConfigurator
45 vpnAPIClient installer.VPNAPIClient
gio8c876172024-10-05 12:25:13 +040046 tasks map[string]*taskForward
giof6ad2982024-08-23 17:42:49 +040047 tmpl tmplts
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040048}
49
50type tmplts struct {
giof6ad2982024-08-23 17:42:49 +040051 index *template.Template
52 app *template.Template
53 allClusters *template.Template
54 cluster *template.Template
55 task *template.Template
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040056}
57
58func parseTemplatesAppManager(fs embed.FS) (tmplts, error) {
59 base, err := template.New("base.html").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(fs, "appmanager-tmpl/base.html")
60 if err != nil {
61 return tmplts{}, err
62 }
63 parse := func(path string) (*template.Template, error) {
64 if b, err := base.Clone(); err != nil {
65 return nil, err
66 } else {
67 return b.ParseFS(fs, path)
68 }
69 }
70 index, err := parse("appmanager-tmpl/index.html")
71 if err != nil {
72 return tmplts{}, err
73 }
74 app, err := parse("appmanager-tmpl/app.html")
75 if err != nil {
76 return tmplts{}, err
77 }
giof6ad2982024-08-23 17:42:49 +040078 allClusters, err := parse("appmanager-tmpl/all-clusters.html")
79 if err != nil {
80 return tmplts{}, err
81 }
82 cluster, err := parse("appmanager-tmpl/cluster.html")
83 if err != nil {
84 return tmplts{}, err
85 }
86 task, err := parse("appmanager-tmpl/task.html")
87 if err != nil {
88 return tmplts{}, err
89 }
90 return tmplts{index, app, allClusters, cluster, task}, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040091}
92
93func NewAppManagerServer(
94 port int,
giof6ad2982024-08-23 17:42:49 +040095 repo soft.RepoIO,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040096 m *installer.AppManager,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040097 r installer.AppRepository,
giof6ad2982024-08-23 17:42:49 +040098 fr installer.AppRepository,
gio43b0f422024-08-21 10:40:13 +040099 reconciler *tasks.FixedReconciler,
gio778577f2024-04-29 09:44:38 +0400100 h installer.HelmReleaseMonitor,
giof6ad2982024-08-23 17:42:49 +0400101 cnc installer.ClusterNetworkConfigurator,
102 vpnAPIClient installer.VPNAPIClient,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400103) (*AppManagerServer, error) {
104 tmpl, err := parseTemplatesAppManager(appTmpls)
105 if err != nil {
106 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400107 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400108 return &AppManagerServer{
giof6ad2982024-08-23 17:42:49 +0400109 l: &sync.Mutex{},
110 port: port,
111 repo: repo,
112 m: m,
113 r: r,
114 fr: fr,
115 reconciler: reconciler,
116 h: h,
117 cnc: cnc,
118 vpnAPIClient: vpnAPIClient,
gio8c876172024-10-05 12:25:13 +0400119 tasks: make(map[string]*taskForward),
giof6ad2982024-08-23 17:42:49 +0400120 tmpl: tmpl,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400121 }, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400122}
123
gio09f8efa2024-06-10 22:35:24 +0400124type cachingHandler struct {
125 h http.Handler
126}
127
128func (h cachingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
129 w.Header().Set("Cache-Control", "max-age=604800")
130 h.h.ServeHTTP(w, r)
131}
132
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400133func (s *AppManagerServer) Start() error {
gioaa0fcdb2024-06-10 22:19:25 +0400134 r := mux.NewRouter()
gio1bf00802024-08-17 12:31:41 +0400135 r.PathPrefix("/stat/").Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
giocb34ad22024-07-11 08:01:13 +0400136 r.HandleFunc("/api/networks", s.handleNetworks).Methods(http.MethodGet)
giof15b9da2024-09-19 06:59:16 +0400137 r.HandleFunc("/api/clusters", s.handleClusters).Methods(http.MethodGet)
138 r.HandleFunc("/api/proxy/add", s.handleProxyAdd).Methods(http.MethodPost)
139 r.HandleFunc("/api/proxy/remove", s.handleProxyRemove).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400140 r.HandleFunc("/api/app-repo", s.handleAppRepo)
141 r.HandleFunc("/api/app/{slug}/install", s.handleAppInstall).Methods(http.MethodPost)
142 r.HandleFunc("/api/app/{slug}", s.handleApp).Methods(http.MethodGet)
143 r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
144 r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
145 r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400146 r.HandleFunc("/clusters/{cluster}/servers/{server}/remove", s.handleClusterRemoveServer).Methods(http.MethodPost)
147 r.HandleFunc("/clusters/{cluster}/servers", s.handleClusterAddServer).Methods(http.MethodPost)
148 r.HandleFunc("/clusters/{name}", s.handleCluster).Methods(http.MethodGet)
gio8f290322024-09-21 15:37:45 +0400149 r.HandleFunc("/clusters/{name}/setup-storage", s.handleClusterSetupStorage).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400150 r.HandleFunc("/clusters/{name}/remove", s.handleRemoveCluster).Methods(http.MethodPost)
151 r.HandleFunc("/clusters", s.handleAllClusters).Methods(http.MethodGet)
152 r.HandleFunc("/clusters", s.handleCreateCluster).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400153 r.HandleFunc("/app/{slug}", s.handleAppUI).Methods(http.MethodGet)
154 r.HandleFunc("/instance/{slug}", s.handleInstanceUI).Methods(http.MethodGet)
giof6ad2982024-08-23 17:42:49 +0400155 r.HandleFunc("/tasks/{slug}", s.handleTaskStatus).Methods(http.MethodGet)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400156 r.HandleFunc("/{pageType}", s.handleAppsList).Methods(http.MethodGet)
157 r.HandleFunc("/", s.handleAppsList).Methods(http.MethodGet)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400158 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
gioaa0fcdb2024-06-10 22:19:25 +0400159 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400160}
161
giocb34ad22024-07-11 08:01:13 +0400162func (s *AppManagerServer) handleNetworks(w http.ResponseWriter, r *http.Request) {
163 env, err := s.m.Config()
164 if err != nil {
165 http.Error(w, err.Error(), http.StatusInternalServerError)
166 return
167 }
168 networks, err := s.m.CreateNetworks(env)
169 if err != nil {
170 http.Error(w, err.Error(), http.StatusInternalServerError)
171 return
172 }
173 if err := json.NewEncoder(w).Encode(networks); err != nil {
174 http.Error(w, err.Error(), http.StatusInternalServerError)
175 return
176 }
177}
178
giof15b9da2024-09-19 06:59:16 +0400179func (s *AppManagerServer) handleClusters(w http.ResponseWriter, r *http.Request) {
180 clusters, err := s.m.GetClusters()
181 if err != nil {
182 http.Error(w, err.Error(), http.StatusInternalServerError)
183 return
184 }
185 if err := json.NewEncoder(w).Encode(installer.ToAccessConfigs(clusters)); err != nil {
186 http.Error(w, err.Error(), http.StatusInternalServerError)
187 return
188 }
189}
190
191type proxyPair struct {
192 From string `json:"from"`
193 To string `json:"to"`
194}
195
196func (s *AppManagerServer) handleProxyAdd(w http.ResponseWriter, r *http.Request) {
197 var req proxyPair
198 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
199 http.Error(w, err.Error(), http.StatusBadRequest)
200 return
201 }
202 if err := s.cnc.AddProxy(req.From, req.To); err != nil {
203 http.Error(w, err.Error(), http.StatusInternalServerError)
204 return
205 }
206}
207
208func (s *AppManagerServer) handleProxyRemove(w http.ResponseWriter, r *http.Request) {
209 var req proxyPair
210 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
211 http.Error(w, err.Error(), http.StatusBadRequest)
212 return
213 }
214 if err := s.cnc.RemoveProxy(req.From, req.To); err != nil {
215 http.Error(w, err.Error(), http.StatusInternalServerError)
216 return
217 }
218}
219
220type app struct {
221 Name string `json:"name"`
222 Icon template.HTML `json:"icon"`
223 ShortDescription string `json:"shortDescription"`
224 Slug string `json:"slug"`
225 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
226}
227
gioaa0fcdb2024-06-10 22:19:25 +0400228func (s *AppManagerServer) handleAppRepo(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229 all, err := s.r.GetAll()
230 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400231 http.Error(w, err.Error(), http.StatusInternalServerError)
232 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400233 }
234 resp := make([]app, len(all))
235 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +0400236 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400237 }
gioaa0fcdb2024-06-10 22:19:25 +0400238 w.Header().Set("Content-Type", "application/json")
239 if err := json.NewEncoder(w).Encode(resp); err != nil {
240 http.Error(w, err.Error(), http.StatusInternalServerError)
241 return
242 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400243}
244
gioaa0fcdb2024-06-10 22:19:25 +0400245func (s *AppManagerServer) handleApp(w http.ResponseWriter, r *http.Request) {
246 slug, ok := mux.Vars(r)["slug"]
247 if !ok {
248 http.Error(w, "empty slug", http.StatusBadRequest)
249 return
250 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400251 a, err := s.r.Find(slug)
252 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400253 http.Error(w, err.Error(), http.StatusInternalServerError)
254 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400255 }
gio7fbd4ad2024-08-27 10:06:39 +0400256 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400257 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400258 http.Error(w, err.Error(), http.StatusInternalServerError)
259 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400260 }
gioaa0fcdb2024-06-10 22:19:25 +0400261 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances}
262 w.Header().Set("Content-Type", "application/json")
263 if err := json.NewEncoder(w).Encode(resp); err != nil {
264 http.Error(w, err.Error(), http.StatusInternalServerError)
265 return
266 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400267}
268
gioaa0fcdb2024-06-10 22:19:25 +0400269func (s *AppManagerServer) handleInstance(w http.ResponseWriter, r *http.Request) {
270 slug, ok := mux.Vars(r)["slug"]
271 if !ok {
272 http.Error(w, "empty slug", http.StatusBadRequest)
273 return
274 }
gio7fbd4ad2024-08-27 10:06:39 +0400275 instance, err := s.m.GetInstance(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400276 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400277 http.Error(w, err.Error(), http.StatusInternalServerError)
278 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400279 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400280 a, err := s.r.Find(instance.AppId)
281 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400282 http.Error(w, err.Error(), http.StatusInternalServerError)
283 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400284 }
gioaa0fcdb2024-06-10 22:19:25 +0400285 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), []installer.AppInstanceConfig{*instance}}
286 w.Header().Set("Content-Type", "application/json")
287 if err := json.NewEncoder(w).Encode(resp); err != nil {
288 http.Error(w, err.Error(), http.StatusInternalServerError)
289 return
290 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400291}
292
gioaa0fcdb2024-06-10 22:19:25 +0400293func (s *AppManagerServer) handleAppInstall(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400294 s.l.Lock()
295 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400296 slug, ok := mux.Vars(r)["slug"]
297 if !ok {
298 http.Error(w, "empty slug", http.StatusBadRequest)
299 return
300 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400301 var values map[string]any
gio8c876172024-10-05 12:25:13 +0400302 if err := json.NewDecoder(r.Body).Decode(&values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400303 http.Error(w, err.Error(), http.StatusInternalServerError)
304 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400305 }
gio3cdee592024-04-17 10:15:56 +0400306 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400307 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400308 http.Error(w, err.Error(), http.StatusInternalServerError)
309 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400310 }
gio3cdee592024-04-17 10:15:56 +0400311 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400312 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400313 http.Error(w, err.Error(), http.StatusInternalServerError)
314 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400315 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400316 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400317 suffix, err := suffixGen.Generate()
318 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400319 http.Error(w, err.Error(), http.StatusInternalServerError)
320 return
gio3af43942024-04-16 08:13:50 +0400321 }
gio44f621b2024-04-29 09:44:38 +0400322 instanceId := a.Slug() + suffix
gio3cdee592024-04-17 10:15:56 +0400323 appDir := fmt.Sprintf("/apps/%s", instanceId)
324 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
gio1cd65152024-08-16 08:18:49 +0400325 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
gio43b0f422024-08-21 10:40:13 +0400326 rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
327 if err == nil {
328 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
329 go s.reconciler.Reconcile(ctx)
330 }
331 return rr, err
gio1cd65152024-08-16 08:18:49 +0400332 })
gio778577f2024-04-29 09:44:38 +0400333 if _, ok := s.tasks[instanceId]; ok {
334 panic("MUST NOT REACH!")
335 }
gio8c876172024-10-05 12:25:13 +0400336 s.tasks[instanceId] = &taskForward{t, fmt.Sprintf("/instance/%s", instanceId), 0}
337 t.OnDone(s.cleanTask(instanceId, 0))
gio778577f2024-04-29 09:44:38 +0400338 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400339 if _, err := fmt.Fprintf(w, "/tasks/%s", instanceId); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400340 http.Error(w, err.Error(), http.StatusInternalServerError)
341 return
342 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400343}
344
gioaa0fcdb2024-06-10 22:19:25 +0400345func (s *AppManagerServer) handleAppUpdate(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400346 s.l.Lock()
347 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400348 slug, ok := mux.Vars(r)["slug"]
349 if !ok {
350 http.Error(w, "empty slug", http.StatusBadRequest)
351 return
352 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400353 var values map[string]any
gio8c876172024-10-05 12:25:13 +0400354 if err := json.NewDecoder(r.Body).Decode(&values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400355 http.Error(w, err.Error(), http.StatusInternalServerError)
356 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400357 }
gio8c876172024-10-05 12:25:13 +0400358 tid := 0
359 if t, ok := s.tasks[slug]; ok {
360 if t.task != nil {
361 http.Error(w, "Update already in progress", http.StatusBadRequest)
362 return
363 }
364 tid = t.id + 1
gio778577f2024-04-29 09:44:38 +0400365 }
giof8843412024-05-22 16:38:05 +0400366 rr, err := s.m.Update(slug, values)
gio778577f2024-04-29 09:44:38 +0400367 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400368 http.Error(w, err.Error(), http.StatusInternalServerError)
369 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400370 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400371 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
372 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400373 t := tasks.NewMonitorRelease(s.h, rr)
gio8c876172024-10-05 12:25:13 +0400374 t.OnDone(s.cleanTask(slug, tid))
375 s.tasks[slug] = &taskForward{t, fmt.Sprintf("/instance/%s", slug), tid}
gio778577f2024-04-29 09:44:38 +0400376 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400377 if _, err := fmt.Fprintf(w, "/tasks/%s", slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400378 http.Error(w, err.Error(), http.StatusInternalServerError)
379 return
380 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400381}
382
gioaa0fcdb2024-06-10 22:19:25 +0400383func (s *AppManagerServer) handleAppRemove(w http.ResponseWriter, r *http.Request) {
384 slug, ok := mux.Vars(r)["slug"]
385 if !ok {
386 http.Error(w, "empty slug", http.StatusBadRequest)
387 return
388 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400389 if err := s.m.Remove(slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400390 http.Error(w, err.Error(), http.StatusInternalServerError)
391 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400392 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400393 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
394 go s.reconciler.Reconcile(ctx)
gioaa0fcdb2024-06-10 22:19:25 +0400395 if _, err := fmt.Fprint(w, "/"); err != nil {
396 http.Error(w, err.Error(), http.StatusInternalServerError)
397 return
398 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400399}
400
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400401type PageData struct {
Davit Tabidze780a0d02024-08-05 20:53:26 +0400402 Apps []app
403 CurrentPage string
404 SearchTarget string
405 SearchValue string
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400406}
407
Davit Tabidze780a0d02024-08-05 20:53:26 +0400408func (s *AppManagerServer) handleAppsList(w http.ResponseWriter, r *http.Request) {
409 pageType := mux.Vars(r)["pageType"]
410 if pageType == "" {
411 pageType = "all"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400412 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400413 searchQuery := r.FormValue("query")
414 apps, err := s.r.Filter(searchQuery)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400415 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400416 http.Error(w, err.Error(), http.StatusInternalServerError)
417 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400418 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400419 resp := make([]app, 0)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400420 for _, a := range apps {
gio7fbd4ad2024-08-27 10:06:39 +0400421 instances, err := s.m.GetAllAppInstances(a.Slug())
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400422 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400423 http.Error(w, err.Error(), http.StatusInternalServerError)
424 return
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400425 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400426 switch pageType {
427 case "installed":
428 if len(instances) != 0 {
429 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
430 }
431 case "not-installed":
432 if len(instances) == 0 {
433 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
434 }
435 default:
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400436 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
437 }
438 }
439 data := PageData{
Davit Tabidze780a0d02024-08-05 20:53:26 +0400440 Apps: resp,
441 CurrentPage: pageType,
442 SearchTarget: pageType,
443 SearchValue: searchQuery,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400444 }
gioaa0fcdb2024-06-10 22:19:25 +0400445 if err := s.tmpl.index.Execute(w, data); err != nil {
446 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400447 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400448}
449
450type appPageData struct {
gio3cdee592024-04-17 10:15:56 +0400451 App installer.EnvApp
452 Instance *installer.AppInstanceConfig
453 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400454 AvailableNetworks []installer.Network
giof6ad2982024-08-23 17:42:49 +0400455 AvailableClusters []cluster.State
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400456 CurrentPage string
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400457}
458
gioaa0fcdb2024-06-10 22:19:25 +0400459func (s *AppManagerServer) handleAppUI(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400460 global, err := s.m.Config()
461 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400462 http.Error(w, err.Error(), http.StatusInternalServerError)
463 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400464 }
gioaa0fcdb2024-06-10 22:19:25 +0400465 slug, ok := mux.Vars(r)["slug"]
466 if !ok {
467 http.Error(w, "empty slug", http.StatusBadRequest)
468 return
469 }
gio3cdee592024-04-17 10:15:56 +0400470 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400471 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400472 http.Error(w, err.Error(), http.StatusInternalServerError)
473 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400474 }
gio7fbd4ad2024-08-27 10:06:39 +0400475 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400476 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400477 http.Error(w, err.Error(), http.StatusInternalServerError)
478 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400479 }
giocb34ad22024-07-11 08:01:13 +0400480 networks, err := s.m.CreateNetworks(global)
481 if err != nil {
482 http.Error(w, err.Error(), http.StatusInternalServerError)
483 return
484 }
giof6ad2982024-08-23 17:42:49 +0400485 clusters, err := s.m.GetClusters()
486 if err != nil {
487 http.Error(w, err.Error(), http.StatusInternalServerError)
488 return
489 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400490 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400491 App: a,
492 Instances: instances,
giocb34ad22024-07-11 08:01:13 +0400493 AvailableNetworks: networks,
giof6ad2982024-08-23 17:42:49 +0400494 AvailableClusters: clusters,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400495 CurrentPage: a.Name(),
496 }
gioaa0fcdb2024-06-10 22:19:25 +0400497 if err := s.tmpl.app.Execute(w, data); err != nil {
498 http.Error(w, err.Error(), http.StatusInternalServerError)
499 return
500 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400501}
502
gioaa0fcdb2024-06-10 22:19:25 +0400503func (s *AppManagerServer) handleInstanceUI(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400504 s.l.Lock()
505 defer s.l.Unlock()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400506 global, err := s.m.Config()
507 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400508 http.Error(w, err.Error(), http.StatusInternalServerError)
509 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400510 }
gioaa0fcdb2024-06-10 22:19:25 +0400511 slug, ok := mux.Vars(r)["slug"]
512 if !ok {
513 http.Error(w, "empty slug", http.StatusBadRequest)
514 return
515 }
gio8c876172024-10-05 12:25:13 +0400516 if t, ok := s.tasks[slug]; ok && t.task != nil {
giof6ad2982024-08-23 17:42:49 +0400517 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", slug), http.StatusSeeOther)
518 return
519 }
gio8c876172024-10-05 12:25:13 +0400520 instance, err := s.m.GetInstance(slug)
521 if err != nil {
522 http.Error(w, err.Error(), http.StatusInternalServerError)
523 return
524 }
525 a, err := s.m.GetInstanceApp(instance.Id)
526 if err != nil {
527 http.Error(w, err.Error(), http.StatusInternalServerError)
528 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400529 }
gio7fbd4ad2024-08-27 10:06:39 +0400530 instances, err := s.m.GetAllAppInstances(a.Slug())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400531 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400532 http.Error(w, err.Error(), http.StatusInternalServerError)
533 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400534 }
giocb34ad22024-07-11 08:01:13 +0400535 networks, err := s.m.CreateNetworks(global)
536 if err != nil {
537 http.Error(w, err.Error(), http.StatusInternalServerError)
538 return
539 }
giof6ad2982024-08-23 17:42:49 +0400540 clusters, err := s.m.GetClusters()
541 if err != nil {
542 http.Error(w, err.Error(), http.StatusInternalServerError)
543 return
544 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400545 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400546 App: a,
gio778577f2024-04-29 09:44:38 +0400547 Instance: instance,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400548 Instances: instances,
giocb34ad22024-07-11 08:01:13 +0400549 AvailableNetworks: networks,
giof6ad2982024-08-23 17:42:49 +0400550 AvailableClusters: clusters,
gio1cd65152024-08-16 08:18:49 +0400551 CurrentPage: slug,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400552 }
gioaa0fcdb2024-06-10 22:19:25 +0400553 if err := s.tmpl.app.Execute(w, data); err != nil {
554 http.Error(w, err.Error(), http.StatusInternalServerError)
555 return
556 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400557}
giof6ad2982024-08-23 17:42:49 +0400558
559type taskStatusData struct {
560 CurrentPage string
561 Task tasks.Task
562}
563
564func (s *AppManagerServer) handleTaskStatus(w http.ResponseWriter, r *http.Request) {
565 s.l.Lock()
566 defer s.l.Unlock()
567 slug, ok := mux.Vars(r)["slug"]
568 if !ok {
569 http.Error(w, "empty slug", http.StatusBadRequest)
570 return
571 }
572 t, ok := s.tasks[slug]
573 if !ok {
574 http.Error(w, "task not found", http.StatusInternalServerError)
575
576 return
577 }
gio8c876172024-10-05 12:25:13 +0400578 if ok && t.task == nil {
giof6ad2982024-08-23 17:42:49 +0400579 http.Redirect(w, r, t.redirectTo, http.StatusSeeOther)
580 return
581 }
582 data := taskStatusData{
583 CurrentPage: "",
584 Task: t.task,
585 }
586 if err := s.tmpl.task.Execute(w, data); err != nil {
587 http.Error(w, err.Error(), http.StatusInternalServerError)
588 return
589 }
590}
591
592type clustersData struct {
593 CurrentPage string
594 Clusters []cluster.State
595}
596
597func (s *AppManagerServer) handleAllClusters(w http.ResponseWriter, r *http.Request) {
598 clusters, err := s.m.GetClusters()
599 if err != nil {
600 http.Error(w, err.Error(), http.StatusInternalServerError)
601 return
602 }
603 data := clustersData{
604 "clusters",
605 clusters,
606 }
607 if err := s.tmpl.allClusters.Execute(w, data); err != nil {
608 http.Error(w, err.Error(), http.StatusInternalServerError)
609 return
610 }
611}
612
613type clusterData struct {
614 CurrentPage string
615 Cluster cluster.State
616}
617
618func (s *AppManagerServer) handleCluster(w http.ResponseWriter, r *http.Request) {
619 name, ok := mux.Vars(r)["name"]
620 if !ok {
621 http.Error(w, "empty name", http.StatusBadRequest)
622 return
623 }
624 m, err := s.getClusterManager(name)
625 if err != nil {
626 if errors.Is(err, installer.ErrorNotFound) {
627 http.Error(w, "not found", http.StatusNotFound)
628 } else {
629 http.Error(w, err.Error(), http.StatusInternalServerError)
630 }
631 return
632 }
633 data := clusterData{
634 "clusters",
635 m.State(),
636 }
637 if err := s.tmpl.cluster.Execute(w, data); err != nil {
638 http.Error(w, err.Error(), http.StatusInternalServerError)
639 return
640 }
641}
642
gio8f290322024-09-21 15:37:45 +0400643func (s *AppManagerServer) handleClusterSetupStorage(w http.ResponseWriter, r *http.Request) {
644 cName, ok := mux.Vars(r)["name"]
645 if !ok {
646 http.Error(w, "empty name", http.StatusBadRequest)
647 return
648 }
gio8c876172024-10-05 12:25:13 +0400649 tid := 0
650 if t, ok := s.tasks[cName]; ok {
651 if t.task != nil {
652 http.Error(w, "cluster task in progress", http.StatusLocked)
653 return
654 }
655 tid = t.id + 1
gio8f290322024-09-21 15:37:45 +0400656 }
657 m, err := s.getClusterManager(cName)
658 if err != nil {
659 if errors.Is(err, installer.ErrorNotFound) {
660 http.Error(w, "not found", http.StatusNotFound)
661 } else {
662 http.Error(w, err.Error(), http.StatusInternalServerError)
663 }
664 return
665 }
666 task := tasks.NewClusterSetupTask(m, s.setupRemoteClusterStorage(), s.repo, fmt.Sprintf("cluster %s: setting up storage", m.State().Name))
gio8c876172024-10-05 12:25:13 +0400667 task.OnDone(s.cleanTask(cName, tid))
gio8f290322024-09-21 15:37:45 +0400668 go task.Start()
gio8c876172024-10-05 12:25:13 +0400669 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
gio8f290322024-09-21 15:37:45 +0400670 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
671}
672
giof6ad2982024-08-23 17:42:49 +0400673func (s *AppManagerServer) handleClusterRemoveServer(w http.ResponseWriter, r *http.Request) {
674 s.l.Lock()
675 defer s.l.Unlock()
676 cName, ok := mux.Vars(r)["cluster"]
677 if !ok {
678 http.Error(w, "empty name", http.StatusBadRequest)
679 return
680 }
gio8c876172024-10-05 12:25:13 +0400681 tid := 0
682 if t, ok := s.tasks[cName]; ok {
683 if t.task != nil {
684 http.Error(w, "cluster task in progress", http.StatusLocked)
685 return
686 }
687 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400688 }
689 sName, ok := mux.Vars(r)["server"]
690 if !ok {
691 http.Error(w, "empty name", http.StatusBadRequest)
692 return
693 }
694 m, err := s.getClusterManager(cName)
695 if err != nil {
696 if errors.Is(err, installer.ErrorNotFound) {
697 http.Error(w, "not found", http.StatusNotFound)
698 } else {
699 http.Error(w, err.Error(), http.StatusInternalServerError)
700 }
701 return
702 }
703 task := tasks.NewClusterRemoveServerTask(m, sName, s.repo)
gio8c876172024-10-05 12:25:13 +0400704 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400705 go task.Start()
gio8c876172024-10-05 12:25:13 +0400706 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400707 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
708}
709
710func (s *AppManagerServer) getClusterManager(cName string) (cluster.Manager, error) {
711 clusters, err := s.m.GetClusters()
712 if err != nil {
713 return nil, err
714 }
715 var c *cluster.State
716 for _, i := range clusters {
717 if i.Name == cName {
718 c = &i
719 break
720 }
721 }
722 if c == nil {
723 return nil, installer.ErrorNotFound
724 }
725 return cluster.RestoreKubeManager(*c)
726}
727
728func (s *AppManagerServer) handleClusterAddServer(w http.ResponseWriter, r *http.Request) {
729 s.l.Lock()
730 defer s.l.Unlock()
731 cName, ok := mux.Vars(r)["cluster"]
732 if !ok {
733 http.Error(w, "empty name", http.StatusBadRequest)
734 return
735 }
gio8c876172024-10-05 12:25:13 +0400736 tid := 0
737 if t, ok := s.tasks[cName]; ok {
738 if t.task != nil {
739 http.Error(w, "cluster task in progress", http.StatusLocked)
740 return
741 }
742 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400743 }
744 m, err := s.getClusterManager(cName)
745 if err != nil {
746 if errors.Is(err, installer.ErrorNotFound) {
747 http.Error(w, "not found", http.StatusNotFound)
748 } else {
749 http.Error(w, err.Error(), http.StatusInternalServerError)
750 }
751 return
752 }
753 t := r.PostFormValue("type")
gio8f290322024-09-21 15:37:45 +0400754 ip := net.ParseIP(strings.TrimSpace(r.PostFormValue("ip")))
giof6ad2982024-08-23 17:42:49 +0400755 if ip == nil {
756 http.Error(w, "invalid ip", http.StatusBadRequest)
757 return
758 }
759 port := 22
760 if p := r.PostFormValue("port"); p != "" {
761 port, err = strconv.Atoi(p)
762 if err != nil {
763 http.Error(w, err.Error(), http.StatusBadRequest)
764 return
765 }
766 }
767 server := cluster.Server{
768 IP: ip,
769 Port: port,
770 User: r.PostFormValue("user"),
771 Password: r.PostFormValue("password"),
772 }
773 var task tasks.Task
774 switch strings.ToLower(t) {
775 case "controller":
776 if len(m.State().Controllers) == 0 {
777 task = tasks.NewClusterInitTask(m, server, s.cnc, s.repo, s.setupRemoteCluster())
778 } else {
779 task = tasks.NewClusterJoinControllerTask(m, server, s.repo)
780 }
781 case "worker":
782 task = tasks.NewClusterJoinWorkerTask(m, server, s.repo)
783 default:
784 http.Error(w, "invalid type", http.StatusBadRequest)
785 return
786 }
gio8c876172024-10-05 12:25:13 +0400787 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400788 go task.Start()
gio8c876172024-10-05 12:25:13 +0400789 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400790 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
791}
792
793func (s *AppManagerServer) handleCreateCluster(w http.ResponseWriter, r *http.Request) {
794 cName := r.PostFormValue("name")
795 if cName == "" {
796 http.Error(w, "no name", http.StatusBadRequest)
797 return
798 }
799 st := cluster.State{Name: cName}
800 if _, err := s.repo.Do(func(fs soft.RepoFS) (string, error) {
801 if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", cName), st); err != nil {
802 return "", err
803 }
804 return fmt.Sprintf("create cluster: %s", cName), nil
805 }); err != nil {
806 http.Error(w, err.Error(), http.StatusInternalServerError)
807 return
808 }
809 http.Redirect(w, r, fmt.Sprintf("/clusters/%s", cName), http.StatusSeeOther)
810}
811
812func (s *AppManagerServer) handleRemoveCluster(w http.ResponseWriter, r *http.Request) {
813 cName, ok := mux.Vars(r)["name"]
814 if !ok {
815 http.Error(w, "empty name", http.StatusBadRequest)
816 return
817 }
gio8c876172024-10-05 12:25:13 +0400818 tid := 0
819 if t, ok := s.tasks[cName]; ok {
820 if t.task != nil {
821 http.Error(w, "cluster task in progress", http.StatusLocked)
822 return
823 }
824 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400825 }
826 m, err := s.getClusterManager(cName)
827 if err != nil {
828 if errors.Is(err, installer.ErrorNotFound) {
829 http.Error(w, "not found", http.StatusNotFound)
830 } else {
831 http.Error(w, err.Error(), http.StatusInternalServerError)
832 }
833 return
834 }
835 task := tasks.NewRemoveClusterTask(m, s.cnc, s.repo)
gio8c876172024-10-05 12:25:13 +0400836 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400837 go task.Start()
gio8c876172024-10-05 12:25:13 +0400838 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400839 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
840}
841
gio8f290322024-09-21 15:37:45 +0400842func (s *AppManagerServer) setupRemoteCluster() cluster.ClusterIngressSetupFunc {
giof6ad2982024-08-23 17:42:49 +0400843 const vpnUser = "private-network-proxy"
844 return func(name, kubeconfig, ingressClassName string) (net.IP, error) {
845 hostname := fmt.Sprintf("cluster-%s", name)
846 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
847 app, err := installer.FindEnvApp(s.fr, "cluster-network")
848 if err != nil {
849 return installer.ReleaseResources{}, err
850 }
851 env, err := s.m.Config()
852 if err != nil {
853 return installer.ReleaseResources{}, err
854 }
855 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
856 appDir := fmt.Sprintf("/clusters/%s/ingress", name)
gio8f290322024-09-21 15:37:45 +0400857 namespace := fmt.Sprintf("%scluster-%s-network", env.NamespacePrefix, name)
giof6ad2982024-08-23 17:42:49 +0400858 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
859 "cluster": map[string]any{
860 "name": name,
861 "kubeconfig": kubeconfig,
862 "ingressClassName": ingressClassName,
863 },
864 // TODO(gio): remove hardcoded user
865 "vpnUser": vpnUser,
866 "vpnProxyHostname": hostname,
867 })
868 if err != nil {
869 return installer.ReleaseResources{}, err
870 }
871 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
872 go s.reconciler.Reconcile(ctx)
873 return rr, err
874 })
875 ch := make(chan error)
876 t.OnDone(func(err error) {
877 ch <- err
878 })
879 go t.Start()
880 err := <-ch
881 if err != nil {
882 return nil, err
883 }
884 for {
885 ip, err := s.vpnAPIClient.GetNodeIP(vpnUser, hostname)
886 if err == nil {
887 return ip, nil
888 }
889 if errors.Is(err, installer.ErrorNotFound) {
890 time.Sleep(5 * time.Second)
891 }
892 }
893 }
894}
gio8f290322024-09-21 15:37:45 +0400895
896func (s *AppManagerServer) setupRemoteClusterStorage() cluster.ClusterSetupFunc {
897 return func(cm cluster.Manager) error {
898 name := cm.State().Name
899 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
900 app, err := installer.FindEnvApp(s.fr, "longhorn")
901 if err != nil {
902 return installer.ReleaseResources{}, err
903 }
904 env, err := s.m.Config()
905 if err != nil {
906 return installer.ReleaseResources{}, err
907 }
908 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
909 appDir := fmt.Sprintf("/clusters/%s/storage", name)
910 namespace := fmt.Sprintf("%scluster-%s-storage", env.NamespacePrefix, name)
911 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
912 "cluster": name,
913 })
914 if err != nil {
915 return installer.ReleaseResources{}, err
916 }
917 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
918 go s.reconciler.Reconcile(ctx)
919 return rr, err
920 })
921 ch := make(chan error)
922 t.OnDone(func(err error) {
923 ch <- err
924 })
925 go t.Start()
926 err := <-ch
927 if err != nil {
928 return err
929 }
930 cm.EnableStorage()
931 return nil
932 }
933}
gio8c876172024-10-05 12:25:13 +0400934
935func (s *AppManagerServer) cleanTask(name string, id int) func(error) {
936 return func(err error) {
937 if err != nil {
938 fmt.Printf("Task %s failed: %s", name, err.Error())
939 }
940 s.l.Lock()
941 defer s.l.Unlock()
942 s.tasks[name].task = nil
943 go func() {
944 time.Sleep(30 * time.Second)
945 s.l.Lock()
946 defer s.l.Unlock()
947 if t, ok := s.tasks[name]; ok && t.id == id {
948 delete(s.tasks, name)
949 }
950 }()
951 }
952}