blob: 190ca12431e4f4dcec4c1caec808c80fa507f5d4 [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"
10 "io/ioutil"
11 "log"
giof6ad2982024-08-23 17:42:49 +040012 "net"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040013 "net/http"
giof6ad2982024-08-23 17:42:49 +040014 "strconv"
15 "strings"
16 "sync"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040017 "time"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040018
19 "github.com/Masterminds/sprig/v3"
gioaa0fcdb2024-06-10 22:19:25 +040020 "github.com/gorilla/mux"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040021
22 "github.com/giolekva/pcloud/core/installer"
giof6ad2982024-08-23 17:42:49 +040023 "github.com/giolekva/pcloud/core/installer/cluster"
24 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040025 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040026)
27
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040028//go:embed appmanager-tmpl/*
29var appTmpls embed.FS
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040030
giof6ad2982024-08-23 17:42:49 +040031type taskForward struct {
32 task tasks.Task
33 redirectTo string
34}
35
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040036type AppManagerServer struct {
giof6ad2982024-08-23 17:42:49 +040037 l sync.Locker
38 port int
39 repo soft.RepoIO
40 m *installer.AppManager
41 r installer.AppRepository
42 fr installer.AppRepository
43 reconciler *tasks.FixedReconciler
44 h installer.HelmReleaseMonitor
45 cnc installer.ClusterNetworkConfigurator
46 vpnAPIClient installer.VPNAPIClient
47 tasks map[string]taskForward
48 ta map[string]installer.EnvApp
49 tmpl tmplts
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040050}
51
52type tmplts struct {
giof6ad2982024-08-23 17:42:49 +040053 index *template.Template
54 app *template.Template
55 allClusters *template.Template
56 cluster *template.Template
57 task *template.Template
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040058}
59
60func parseTemplatesAppManager(fs embed.FS) (tmplts, error) {
61 base, err := template.New("base.html").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(fs, "appmanager-tmpl/base.html")
62 if err != nil {
63 return tmplts{}, err
64 }
65 parse := func(path string) (*template.Template, error) {
66 if b, err := base.Clone(); err != nil {
67 return nil, err
68 } else {
69 return b.ParseFS(fs, path)
70 }
71 }
72 index, err := parse("appmanager-tmpl/index.html")
73 if err != nil {
74 return tmplts{}, err
75 }
76 app, err := parse("appmanager-tmpl/app.html")
77 if err != nil {
78 return tmplts{}, err
79 }
giof6ad2982024-08-23 17:42:49 +040080 allClusters, err := parse("appmanager-tmpl/all-clusters.html")
81 if err != nil {
82 return tmplts{}, err
83 }
84 cluster, err := parse("appmanager-tmpl/cluster.html")
85 if err != nil {
86 return tmplts{}, err
87 }
88 task, err := parse("appmanager-tmpl/task.html")
89 if err != nil {
90 return tmplts{}, err
91 }
92 return tmplts{index, app, allClusters, cluster, task}, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040093}
94
95func NewAppManagerServer(
96 port int,
giof6ad2982024-08-23 17:42:49 +040097 repo soft.RepoIO,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040098 m *installer.AppManager,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040099 r installer.AppRepository,
giof6ad2982024-08-23 17:42:49 +0400100 fr installer.AppRepository,
gio43b0f422024-08-21 10:40:13 +0400101 reconciler *tasks.FixedReconciler,
gio778577f2024-04-29 09:44:38 +0400102 h installer.HelmReleaseMonitor,
giof6ad2982024-08-23 17:42:49 +0400103 cnc installer.ClusterNetworkConfigurator,
104 vpnAPIClient installer.VPNAPIClient,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400105) (*AppManagerServer, error) {
106 tmpl, err := parseTemplatesAppManager(appTmpls)
107 if err != nil {
108 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400109 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400110 return &AppManagerServer{
giof6ad2982024-08-23 17:42:49 +0400111 l: &sync.Mutex{},
112 port: port,
113 repo: repo,
114 m: m,
115 r: r,
116 fr: fr,
117 reconciler: reconciler,
118 h: h,
119 cnc: cnc,
120 vpnAPIClient: vpnAPIClient,
121 tasks: make(map[string]taskForward),
122 ta: make(map[string]installer.EnvApp),
123 tmpl: tmpl,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400124 }, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400125}
126
gio09f8efa2024-06-10 22:35:24 +0400127type cachingHandler struct {
128 h http.Handler
129}
130
131func (h cachingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
132 w.Header().Set("Cache-Control", "max-age=604800")
133 h.h.ServeHTTP(w, r)
134}
135
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400136func (s *AppManagerServer) Start() error {
gioaa0fcdb2024-06-10 22:19:25 +0400137 r := mux.NewRouter()
gio1bf00802024-08-17 12:31:41 +0400138 r.PathPrefix("/stat/").Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
giocb34ad22024-07-11 08:01:13 +0400139 r.HandleFunc("/api/networks", s.handleNetworks).Methods(http.MethodGet)
giof15b9da2024-09-19 06:59:16 +0400140 r.HandleFunc("/api/clusters", s.handleClusters).Methods(http.MethodGet)
141 r.HandleFunc("/api/proxy/add", s.handleProxyAdd).Methods(http.MethodPost)
142 r.HandleFunc("/api/proxy/remove", s.handleProxyRemove).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400143 r.HandleFunc("/api/app-repo", s.handleAppRepo)
144 r.HandleFunc("/api/app/{slug}/install", s.handleAppInstall).Methods(http.MethodPost)
145 r.HandleFunc("/api/app/{slug}", s.handleApp).Methods(http.MethodGet)
146 r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
147 r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
148 r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400149 r.HandleFunc("/clusters/{cluster}/servers/{server}/remove", s.handleClusterRemoveServer).Methods(http.MethodPost)
150 r.HandleFunc("/clusters/{cluster}/servers", s.handleClusterAddServer).Methods(http.MethodPost)
151 r.HandleFunc("/clusters/{name}", s.handleCluster).Methods(http.MethodGet)
gio8f290322024-09-21 15:37:45 +0400152 r.HandleFunc("/clusters/{name}/setup-storage", s.handleClusterSetupStorage).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400153 r.HandleFunc("/clusters/{name}/remove", s.handleRemoveCluster).Methods(http.MethodPost)
154 r.HandleFunc("/clusters", s.handleAllClusters).Methods(http.MethodGet)
155 r.HandleFunc("/clusters", s.handleCreateCluster).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400156 r.HandleFunc("/app/{slug}", s.handleAppUI).Methods(http.MethodGet)
157 r.HandleFunc("/instance/{slug}", s.handleInstanceUI).Methods(http.MethodGet)
giof6ad2982024-08-23 17:42:49 +0400158 r.HandleFunc("/tasks/{slug}", s.handleTaskStatus).Methods(http.MethodGet)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400159 r.HandleFunc("/{pageType}", s.handleAppsList).Methods(http.MethodGet)
160 r.HandleFunc("/", s.handleAppsList).Methods(http.MethodGet)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400161 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
gioaa0fcdb2024-06-10 22:19:25 +0400162 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400163}
164
giocb34ad22024-07-11 08:01:13 +0400165func (s *AppManagerServer) handleNetworks(w http.ResponseWriter, r *http.Request) {
166 env, err := s.m.Config()
167 if err != nil {
168 http.Error(w, err.Error(), http.StatusInternalServerError)
169 return
170 }
171 networks, err := s.m.CreateNetworks(env)
172 if err != nil {
173 http.Error(w, err.Error(), http.StatusInternalServerError)
174 return
175 }
176 if err := json.NewEncoder(w).Encode(networks); err != nil {
177 http.Error(w, err.Error(), http.StatusInternalServerError)
178 return
179 }
180}
181
giof15b9da2024-09-19 06:59:16 +0400182func (s *AppManagerServer) handleClusters(w http.ResponseWriter, r *http.Request) {
183 clusters, err := s.m.GetClusters()
184 if err != nil {
185 http.Error(w, err.Error(), http.StatusInternalServerError)
186 return
187 }
188 if err := json.NewEncoder(w).Encode(installer.ToAccessConfigs(clusters)); err != nil {
189 http.Error(w, err.Error(), http.StatusInternalServerError)
190 return
191 }
192}
193
194type proxyPair struct {
195 From string `json:"from"`
196 To string `json:"to"`
197}
198
199func (s *AppManagerServer) handleProxyAdd(w http.ResponseWriter, r *http.Request) {
200 var req proxyPair
201 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
202 http.Error(w, err.Error(), http.StatusBadRequest)
203 return
204 }
205 if err := s.cnc.AddProxy(req.From, req.To); err != nil {
206 http.Error(w, err.Error(), http.StatusInternalServerError)
207 return
208 }
209}
210
211func (s *AppManagerServer) handleProxyRemove(w http.ResponseWriter, r *http.Request) {
212 var req proxyPair
213 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
214 http.Error(w, err.Error(), http.StatusBadRequest)
215 return
216 }
217 if err := s.cnc.RemoveProxy(req.From, req.To); err != nil {
218 http.Error(w, err.Error(), http.StatusInternalServerError)
219 return
220 }
221}
222
223type app struct {
224 Name string `json:"name"`
225 Icon template.HTML `json:"icon"`
226 ShortDescription string `json:"shortDescription"`
227 Slug string `json:"slug"`
228 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
229}
230
gioaa0fcdb2024-06-10 22:19:25 +0400231func (s *AppManagerServer) handleAppRepo(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400232 all, err := s.r.GetAll()
233 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400234 http.Error(w, err.Error(), http.StatusInternalServerError)
235 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400236 }
237 resp := make([]app, len(all))
238 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +0400239 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400240 }
gioaa0fcdb2024-06-10 22:19:25 +0400241 w.Header().Set("Content-Type", "application/json")
242 if err := json.NewEncoder(w).Encode(resp); err != nil {
243 http.Error(w, err.Error(), http.StatusInternalServerError)
244 return
245 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400246}
247
gioaa0fcdb2024-06-10 22:19:25 +0400248func (s *AppManagerServer) handleApp(w http.ResponseWriter, r *http.Request) {
249 slug, ok := mux.Vars(r)["slug"]
250 if !ok {
251 http.Error(w, "empty slug", http.StatusBadRequest)
252 return
253 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400254 a, err := s.r.Find(slug)
255 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400256 http.Error(w, err.Error(), http.StatusInternalServerError)
257 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400258 }
gio7fbd4ad2024-08-27 10:06:39 +0400259 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400260 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400261 http.Error(w, err.Error(), http.StatusInternalServerError)
262 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400263 }
gioaa0fcdb2024-06-10 22:19:25 +0400264 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances}
265 w.Header().Set("Content-Type", "application/json")
266 if err := json.NewEncoder(w).Encode(resp); err != nil {
267 http.Error(w, err.Error(), http.StatusInternalServerError)
268 return
269 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400270}
271
gioaa0fcdb2024-06-10 22:19:25 +0400272func (s *AppManagerServer) handleInstance(w http.ResponseWriter, r *http.Request) {
273 slug, ok := mux.Vars(r)["slug"]
274 if !ok {
275 http.Error(w, "empty slug", http.StatusBadRequest)
276 return
277 }
gio7fbd4ad2024-08-27 10:06:39 +0400278 instance, err := s.m.GetInstance(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400279 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400280 http.Error(w, err.Error(), http.StatusInternalServerError)
281 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400282 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400283 a, err := s.r.Find(instance.AppId)
284 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400285 http.Error(w, err.Error(), http.StatusInternalServerError)
286 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400287 }
gioaa0fcdb2024-06-10 22:19:25 +0400288 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), []installer.AppInstanceConfig{*instance}}
289 w.Header().Set("Content-Type", "application/json")
290 if err := json.NewEncoder(w).Encode(resp); err != nil {
291 http.Error(w, err.Error(), http.StatusInternalServerError)
292 return
293 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400294}
295
gioaa0fcdb2024-06-10 22:19:25 +0400296func (s *AppManagerServer) handleAppInstall(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400297 s.l.Lock()
298 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400299 slug, ok := mux.Vars(r)["slug"]
300 if !ok {
301 http.Error(w, "empty slug", http.StatusBadRequest)
302 return
303 }
304 contents, err := ioutil.ReadAll(r.Body)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400305 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400306 http.Error(w, err.Error(), http.StatusInternalServerError)
307 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400308 }
309 var values map[string]any
310 if err := json.Unmarshal(contents, &values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400311 http.Error(w, err.Error(), http.StatusInternalServerError)
312 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400313 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400314 log.Printf("Values: %+v\n", values)
gio3cdee592024-04-17 10:15:56 +0400315 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400316 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400317 http.Error(w, err.Error(), http.StatusInternalServerError)
318 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400319 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400320 log.Printf("Found application: %s\n", slug)
gio3cdee592024-04-17 10:15:56 +0400321 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400322 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400323 http.Error(w, err.Error(), http.StatusInternalServerError)
324 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400325 }
gio3cdee592024-04-17 10:15:56 +0400326 log.Printf("Configuration: %+v\n", env)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400327 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400328 suffix, err := suffixGen.Generate()
329 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400330 http.Error(w, err.Error(), http.StatusInternalServerError)
331 return
gio3af43942024-04-16 08:13:50 +0400332 }
gio44f621b2024-04-29 09:44:38 +0400333 instanceId := a.Slug() + suffix
gio3cdee592024-04-17 10:15:56 +0400334 appDir := fmt.Sprintf("/apps/%s", instanceId)
335 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
gio1cd65152024-08-16 08:18:49 +0400336 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
gio43b0f422024-08-21 10:40:13 +0400337 rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
338 if err == nil {
339 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
340 go s.reconciler.Reconcile(ctx)
341 }
342 return rr, err
gio1cd65152024-08-16 08:18:49 +0400343 })
gio778577f2024-04-29 09:44:38 +0400344 if _, ok := s.tasks[instanceId]; ok {
345 panic("MUST NOT REACH!")
346 }
giof6ad2982024-08-23 17:42:49 +0400347 s.tasks[instanceId] = taskForward{t, fmt.Sprintf("/instance/%s", instanceId)}
gio1cd65152024-08-16 08:18:49 +0400348 s.ta[instanceId] = a
gio778577f2024-04-29 09:44:38 +0400349 t.OnDone(func(err error) {
giof6ad2982024-08-23 17:42:49 +0400350 go func() {
351 time.Sleep(30 * time.Second)
352 s.l.Lock()
353 defer s.l.Unlock()
354 delete(s.tasks, instanceId)
355 delete(s.ta, instanceId)
356 }()
gio778577f2024-04-29 09:44:38 +0400357 })
gio778577f2024-04-29 09:44:38 +0400358 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400359 if _, err := fmt.Fprintf(w, "/tasks/%s", instanceId); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400360 http.Error(w, err.Error(), http.StatusInternalServerError)
361 return
362 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400363}
364
gioaa0fcdb2024-06-10 22:19:25 +0400365func (s *AppManagerServer) handleAppUpdate(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400366 s.l.Lock()
367 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400368 slug, ok := mux.Vars(r)["slug"]
369 if !ok {
370 http.Error(w, "empty slug", http.StatusBadRequest)
371 return
372 }
gioaa0fcdb2024-06-10 22:19:25 +0400373 contents, err := ioutil.ReadAll(r.Body)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400374 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400375 http.Error(w, err.Error(), http.StatusInternalServerError)
376 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400377 }
378 var values map[string]any
379 if err := json.Unmarshal(contents, &values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400380 http.Error(w, err.Error(), http.StatusInternalServerError)
381 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400382 }
gio778577f2024-04-29 09:44:38 +0400383 if _, ok := s.tasks[slug]; ok {
gioaa0fcdb2024-06-10 22:19:25 +0400384 http.Error(w, "Update already in progress", http.StatusBadRequest)
385 return
gio778577f2024-04-29 09:44:38 +0400386 }
giof8843412024-05-22 16:38:05 +0400387 rr, err := s.m.Update(slug, values)
gio778577f2024-04-29 09:44:38 +0400388 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400389 http.Error(w, err.Error(), http.StatusInternalServerError)
390 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400391 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400392 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
393 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400394 t := tasks.NewMonitorRelease(s.h, rr)
395 t.OnDone(func(err error) {
giof6ad2982024-08-23 17:42:49 +0400396 go func() {
397 time.Sleep(30 * time.Second)
398 s.l.Lock()
399 defer s.l.Unlock()
400 delete(s.tasks, slug)
401 }()
gio778577f2024-04-29 09:44:38 +0400402 })
giof6ad2982024-08-23 17:42:49 +0400403 s.tasks[slug] = taskForward{t, fmt.Sprintf("/instance/%s", slug)}
gio778577f2024-04-29 09:44:38 +0400404 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400405 if _, err := fmt.Fprintf(w, "/tasks/%s", slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400406 http.Error(w, err.Error(), http.StatusInternalServerError)
407 return
408 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400409}
410
gioaa0fcdb2024-06-10 22:19:25 +0400411func (s *AppManagerServer) handleAppRemove(w http.ResponseWriter, r *http.Request) {
412 slug, ok := mux.Vars(r)["slug"]
413 if !ok {
414 http.Error(w, "empty slug", http.StatusBadRequest)
415 return
416 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400417 if err := s.m.Remove(slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400418 http.Error(w, err.Error(), http.StatusInternalServerError)
419 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400420 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400421 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
422 go s.reconciler.Reconcile(ctx)
gioaa0fcdb2024-06-10 22:19:25 +0400423 if _, err := fmt.Fprint(w, "/"); err != nil {
424 http.Error(w, err.Error(), http.StatusInternalServerError)
425 return
426 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400427}
428
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400429type PageData struct {
Davit Tabidze780a0d02024-08-05 20:53:26 +0400430 Apps []app
431 CurrentPage string
432 SearchTarget string
433 SearchValue string
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400434}
435
Davit Tabidze780a0d02024-08-05 20:53:26 +0400436func (s *AppManagerServer) handleAppsList(w http.ResponseWriter, r *http.Request) {
437 pageType := mux.Vars(r)["pageType"]
438 if pageType == "" {
439 pageType = "all"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400440 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400441 searchQuery := r.FormValue("query")
442 apps, err := s.r.Filter(searchQuery)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400443 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400444 http.Error(w, err.Error(), http.StatusInternalServerError)
445 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400446 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400447 resp := make([]app, 0)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400448 for _, a := range apps {
gio7fbd4ad2024-08-27 10:06:39 +0400449 instances, err := s.m.GetAllAppInstances(a.Slug())
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400450 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400451 http.Error(w, err.Error(), http.StatusInternalServerError)
452 return
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400453 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400454 switch pageType {
455 case "installed":
456 if len(instances) != 0 {
457 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
458 }
459 case "not-installed":
460 if len(instances) == 0 {
461 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
462 }
463 default:
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400464 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
465 }
466 }
467 data := PageData{
Davit Tabidze780a0d02024-08-05 20:53:26 +0400468 Apps: resp,
469 CurrentPage: pageType,
470 SearchTarget: pageType,
471 SearchValue: searchQuery,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400472 }
gioaa0fcdb2024-06-10 22:19:25 +0400473 if err := s.tmpl.index.Execute(w, data); err != nil {
474 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400475 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400476}
477
478type appPageData struct {
gio3cdee592024-04-17 10:15:56 +0400479 App installer.EnvApp
480 Instance *installer.AppInstanceConfig
481 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400482 AvailableNetworks []installer.Network
giof6ad2982024-08-23 17:42:49 +0400483 AvailableClusters []cluster.State
gio778577f2024-04-29 09:44:38 +0400484 Task tasks.Task
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400485 CurrentPage string
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400486}
487
gioaa0fcdb2024-06-10 22:19:25 +0400488func (s *AppManagerServer) handleAppUI(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400489 global, err := s.m.Config()
490 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400491 http.Error(w, err.Error(), http.StatusInternalServerError)
492 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400493 }
gioaa0fcdb2024-06-10 22:19:25 +0400494 slug, ok := mux.Vars(r)["slug"]
495 if !ok {
496 http.Error(w, "empty slug", http.StatusBadRequest)
497 return
498 }
gio3cdee592024-04-17 10:15:56 +0400499 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400500 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400501 http.Error(w, err.Error(), http.StatusInternalServerError)
502 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400503 }
gio7fbd4ad2024-08-27 10:06:39 +0400504 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400505 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400506 http.Error(w, err.Error(), http.StatusInternalServerError)
507 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400508 }
giocb34ad22024-07-11 08:01:13 +0400509 networks, err := s.m.CreateNetworks(global)
510 if err != nil {
511 http.Error(w, err.Error(), http.StatusInternalServerError)
512 return
513 }
giof6ad2982024-08-23 17:42:49 +0400514 clusters, err := s.m.GetClusters()
515 if err != nil {
516 http.Error(w, err.Error(), http.StatusInternalServerError)
517 return
518 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400519 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400520 App: a,
521 Instances: instances,
giocb34ad22024-07-11 08:01:13 +0400522 AvailableNetworks: networks,
giof6ad2982024-08-23 17:42:49 +0400523 AvailableClusters: clusters,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400524 CurrentPage: a.Name(),
525 }
gioaa0fcdb2024-06-10 22:19:25 +0400526 if err := s.tmpl.app.Execute(w, data); err != nil {
527 http.Error(w, err.Error(), http.StatusInternalServerError)
528 return
529 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400530}
531
gioaa0fcdb2024-06-10 22:19:25 +0400532func (s *AppManagerServer) handleInstanceUI(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400533 s.l.Lock()
534 defer s.l.Unlock()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400535 global, err := s.m.Config()
536 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400537 http.Error(w, err.Error(), http.StatusInternalServerError)
538 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400539 }
gioaa0fcdb2024-06-10 22:19:25 +0400540 slug, ok := mux.Vars(r)["slug"]
541 if !ok {
542 http.Error(w, "empty slug", http.StatusBadRequest)
543 return
544 }
gio1cd65152024-08-16 08:18:49 +0400545 t, ok := s.tasks[slug]
gio7fbd4ad2024-08-27 10:06:39 +0400546 instance, err := s.m.GetInstance(slug)
gio1cd65152024-08-16 08:18:49 +0400547 if err != nil && !ok {
gioaa0fcdb2024-06-10 22:19:25 +0400548 http.Error(w, err.Error(), http.StatusInternalServerError)
549 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400550 }
giof6ad2982024-08-23 17:42:49 +0400551 if ok && !(t.task.Status() == tasks.StatusDone || t.task.Status() == tasks.StatusFailed) {
552 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", slug), http.StatusSeeOther)
553 return
554 }
gio1cd65152024-08-16 08:18:49 +0400555 var a installer.EnvApp
556 if instance != nil {
557 a, err = s.m.GetInstanceApp(instance.Id)
558 if err != nil {
559 http.Error(w, err.Error(), http.StatusInternalServerError)
560 return
561 }
562 } else {
563 var ok bool
564 a, ok = s.ta[slug]
565 if !ok {
566 panic("MUST NOT REACH!")
567 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400568 }
gio7fbd4ad2024-08-27 10:06:39 +0400569 instances, err := s.m.GetAllAppInstances(a.Slug())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400570 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400571 http.Error(w, err.Error(), http.StatusInternalServerError)
572 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400573 }
giocb34ad22024-07-11 08:01:13 +0400574 networks, err := s.m.CreateNetworks(global)
575 if err != nil {
576 http.Error(w, err.Error(), http.StatusInternalServerError)
577 return
578 }
giof6ad2982024-08-23 17:42:49 +0400579 clusters, err := s.m.GetClusters()
580 if err != nil {
581 http.Error(w, err.Error(), http.StatusInternalServerError)
582 return
583 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400584 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400585 App: a,
gio778577f2024-04-29 09:44:38 +0400586 Instance: instance,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400587 Instances: instances,
giocb34ad22024-07-11 08:01:13 +0400588 AvailableNetworks: networks,
giof6ad2982024-08-23 17:42:49 +0400589 AvailableClusters: clusters,
590 Task: t.task,
gio1cd65152024-08-16 08:18:49 +0400591 CurrentPage: slug,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400592 }
gioaa0fcdb2024-06-10 22:19:25 +0400593 if err := s.tmpl.app.Execute(w, data); err != nil {
594 http.Error(w, err.Error(), http.StatusInternalServerError)
595 return
596 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400597}
giof6ad2982024-08-23 17:42:49 +0400598
599type taskStatusData struct {
600 CurrentPage string
601 Task tasks.Task
602}
603
604func (s *AppManagerServer) handleTaskStatus(w http.ResponseWriter, r *http.Request) {
605 s.l.Lock()
606 defer s.l.Unlock()
607 slug, ok := mux.Vars(r)["slug"]
608 if !ok {
609 http.Error(w, "empty slug", http.StatusBadRequest)
610 return
611 }
612 t, ok := s.tasks[slug]
613 if !ok {
614 http.Error(w, "task not found", http.StatusInternalServerError)
615
616 return
617 }
618 if ok && (t.task.Status() == tasks.StatusDone || t.task.Status() == tasks.StatusFailed) {
619 http.Redirect(w, r, t.redirectTo, http.StatusSeeOther)
620 return
621 }
622 data := taskStatusData{
623 CurrentPage: "",
624 Task: t.task,
625 }
626 if err := s.tmpl.task.Execute(w, data); err != nil {
627 http.Error(w, err.Error(), http.StatusInternalServerError)
628 return
629 }
630}
631
632type clustersData struct {
633 CurrentPage string
634 Clusters []cluster.State
635}
636
637func (s *AppManagerServer) handleAllClusters(w http.ResponseWriter, r *http.Request) {
638 clusters, err := s.m.GetClusters()
639 if err != nil {
640 http.Error(w, err.Error(), http.StatusInternalServerError)
641 return
642 }
643 data := clustersData{
644 "clusters",
645 clusters,
646 }
647 if err := s.tmpl.allClusters.Execute(w, data); err != nil {
648 http.Error(w, err.Error(), http.StatusInternalServerError)
649 return
650 }
651}
652
653type clusterData struct {
654 CurrentPage string
655 Cluster cluster.State
656}
657
658func (s *AppManagerServer) handleCluster(w http.ResponseWriter, r *http.Request) {
659 name, ok := mux.Vars(r)["name"]
660 if !ok {
661 http.Error(w, "empty name", http.StatusBadRequest)
662 return
663 }
664 m, err := s.getClusterManager(name)
665 if err != nil {
666 if errors.Is(err, installer.ErrorNotFound) {
667 http.Error(w, "not found", http.StatusNotFound)
668 } else {
669 http.Error(w, err.Error(), http.StatusInternalServerError)
670 }
671 return
672 }
673 data := clusterData{
674 "clusters",
675 m.State(),
676 }
677 if err := s.tmpl.cluster.Execute(w, data); err != nil {
678 http.Error(w, err.Error(), http.StatusInternalServerError)
679 return
680 }
681}
682
gio8f290322024-09-21 15:37:45 +0400683func (s *AppManagerServer) handleClusterSetupStorage(w http.ResponseWriter, r *http.Request) {
684 cName, ok := mux.Vars(r)["name"]
685 if !ok {
686 http.Error(w, "empty name", http.StatusBadRequest)
687 return
688 }
689 if _, ok := s.tasks[cName]; ok {
690 http.Error(w, "cluster task in progress", http.StatusLocked)
691 return
692 }
693 m, err := s.getClusterManager(cName)
694 if err != nil {
695 if errors.Is(err, installer.ErrorNotFound) {
696 http.Error(w, "not found", http.StatusNotFound)
697 } else {
698 http.Error(w, err.Error(), http.StatusInternalServerError)
699 }
700 return
701 }
702 task := tasks.NewClusterSetupTask(m, s.setupRemoteClusterStorage(), s.repo, fmt.Sprintf("cluster %s: setting up storage", m.State().Name))
703 task.OnDone(func(err error) {
704 go func() {
705 time.Sleep(30 * time.Second)
706 s.l.Lock()
707 defer s.l.Unlock()
708 delete(s.tasks, cName)
709 }()
710 })
711 go task.Start()
712 s.tasks[cName] = taskForward{task, fmt.Sprintf("/clusters/%s", cName)}
713 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
714}
715
giof6ad2982024-08-23 17:42:49 +0400716func (s *AppManagerServer) handleClusterRemoveServer(w http.ResponseWriter, r *http.Request) {
717 s.l.Lock()
718 defer s.l.Unlock()
719 cName, ok := mux.Vars(r)["cluster"]
720 if !ok {
721 http.Error(w, "empty name", http.StatusBadRequest)
722 return
723 }
724 if _, ok := s.tasks[cName]; ok {
725 http.Error(w, "cluster task in progress", http.StatusLocked)
726 return
727 }
728 sName, ok := mux.Vars(r)["server"]
729 if !ok {
730 http.Error(w, "empty name", http.StatusBadRequest)
731 return
732 }
733 m, err := s.getClusterManager(cName)
734 if err != nil {
735 if errors.Is(err, installer.ErrorNotFound) {
736 http.Error(w, "not found", http.StatusNotFound)
737 } else {
738 http.Error(w, err.Error(), http.StatusInternalServerError)
739 }
740 return
741 }
742 task := tasks.NewClusterRemoveServerTask(m, sName, s.repo)
743 task.OnDone(func(err error) {
744 go func() {
745 time.Sleep(30 * time.Second)
746 s.l.Lock()
747 defer s.l.Unlock()
748 delete(s.tasks, cName)
749 }()
750 })
751 go task.Start()
752 s.tasks[cName] = taskForward{task, fmt.Sprintf("/clusters/%s", cName)}
753 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
754}
755
756func (s *AppManagerServer) getClusterManager(cName string) (cluster.Manager, error) {
757 clusters, err := s.m.GetClusters()
758 if err != nil {
759 return nil, err
760 }
761 var c *cluster.State
762 for _, i := range clusters {
763 if i.Name == cName {
764 c = &i
765 break
766 }
767 }
768 if c == nil {
769 return nil, installer.ErrorNotFound
770 }
771 return cluster.RestoreKubeManager(*c)
772}
773
774func (s *AppManagerServer) handleClusterAddServer(w http.ResponseWriter, r *http.Request) {
775 s.l.Lock()
776 defer s.l.Unlock()
777 cName, ok := mux.Vars(r)["cluster"]
778 if !ok {
779 http.Error(w, "empty name", http.StatusBadRequest)
780 return
781 }
782 if _, ok := s.tasks[cName]; ok {
783 http.Error(w, "cluster task in progress", http.StatusLocked)
784 return
785 }
786 m, err := s.getClusterManager(cName)
787 if err != nil {
788 if errors.Is(err, installer.ErrorNotFound) {
789 http.Error(w, "not found", http.StatusNotFound)
790 } else {
791 http.Error(w, err.Error(), http.StatusInternalServerError)
792 }
793 return
794 }
795 t := r.PostFormValue("type")
gio8f290322024-09-21 15:37:45 +0400796 ip := net.ParseIP(strings.TrimSpace(r.PostFormValue("ip")))
giof6ad2982024-08-23 17:42:49 +0400797 if ip == nil {
798 http.Error(w, "invalid ip", http.StatusBadRequest)
799 return
800 }
801 port := 22
802 if p := r.PostFormValue("port"); p != "" {
803 port, err = strconv.Atoi(p)
804 if err != nil {
805 http.Error(w, err.Error(), http.StatusBadRequest)
806 return
807 }
808 }
809 server := cluster.Server{
810 IP: ip,
811 Port: port,
812 User: r.PostFormValue("user"),
813 Password: r.PostFormValue("password"),
814 }
815 var task tasks.Task
816 switch strings.ToLower(t) {
817 case "controller":
818 if len(m.State().Controllers) == 0 {
819 task = tasks.NewClusterInitTask(m, server, s.cnc, s.repo, s.setupRemoteCluster())
820 } else {
821 task = tasks.NewClusterJoinControllerTask(m, server, s.repo)
822 }
823 case "worker":
824 task = tasks.NewClusterJoinWorkerTask(m, server, s.repo)
825 default:
826 http.Error(w, "invalid type", http.StatusBadRequest)
827 return
828 }
829 task.OnDone(func(err error) {
830 go func() {
831 time.Sleep(30 * time.Second)
832 s.l.Lock()
833 defer s.l.Unlock()
834 delete(s.tasks, cName)
835 }()
836 })
837 go task.Start()
838 s.tasks[cName] = taskForward{task, fmt.Sprintf("/clusters/%s", cName)}
839 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
840}
841
842func (s *AppManagerServer) handleCreateCluster(w http.ResponseWriter, r *http.Request) {
843 cName := r.PostFormValue("name")
844 if cName == "" {
845 http.Error(w, "no name", http.StatusBadRequest)
846 return
847 }
848 st := cluster.State{Name: cName}
849 if _, err := s.repo.Do(func(fs soft.RepoFS) (string, error) {
850 if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", cName), st); err != nil {
851 return "", err
852 }
853 return fmt.Sprintf("create cluster: %s", cName), nil
854 }); err != nil {
855 http.Error(w, err.Error(), http.StatusInternalServerError)
856 return
857 }
858 http.Redirect(w, r, fmt.Sprintf("/clusters/%s", cName), http.StatusSeeOther)
859}
860
861func (s *AppManagerServer) handleRemoveCluster(w http.ResponseWriter, r *http.Request) {
862 cName, ok := mux.Vars(r)["name"]
863 if !ok {
864 http.Error(w, "empty name", http.StatusBadRequest)
865 return
866 }
867 if _, ok := s.tasks[cName]; ok {
868 http.Error(w, "cluster task in progress", http.StatusLocked)
869 return
870 }
871 m, err := s.getClusterManager(cName)
872 if err != nil {
873 if errors.Is(err, installer.ErrorNotFound) {
874 http.Error(w, "not found", http.StatusNotFound)
875 } else {
876 http.Error(w, err.Error(), http.StatusInternalServerError)
877 }
878 return
879 }
880 task := tasks.NewRemoveClusterTask(m, s.cnc, s.repo)
881 task.OnDone(func(err error) {
882 go func() {
883 time.Sleep(30 * time.Second)
884 s.l.Lock()
885 defer s.l.Unlock()
886 delete(s.tasks, cName)
887 }()
888 })
889 go task.Start()
890 s.tasks[cName] = taskForward{task, fmt.Sprintf("/clusters/%s", cName)}
891 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
892}
893
gio8f290322024-09-21 15:37:45 +0400894func (s *AppManagerServer) setupRemoteCluster() cluster.ClusterIngressSetupFunc {
giof6ad2982024-08-23 17:42:49 +0400895 const vpnUser = "private-network-proxy"
896 return func(name, kubeconfig, ingressClassName string) (net.IP, error) {
897 hostname := fmt.Sprintf("cluster-%s", name)
898 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
899 app, err := installer.FindEnvApp(s.fr, "cluster-network")
900 if err != nil {
901 return installer.ReleaseResources{}, err
902 }
903 env, err := s.m.Config()
904 if err != nil {
905 return installer.ReleaseResources{}, err
906 }
907 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
908 appDir := fmt.Sprintf("/clusters/%s/ingress", name)
gio8f290322024-09-21 15:37:45 +0400909 namespace := fmt.Sprintf("%scluster-%s-network", env.NamespacePrefix, name)
giof6ad2982024-08-23 17:42:49 +0400910 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
911 "cluster": map[string]any{
912 "name": name,
913 "kubeconfig": kubeconfig,
914 "ingressClassName": ingressClassName,
915 },
916 // TODO(gio): remove hardcoded user
917 "vpnUser": vpnUser,
918 "vpnProxyHostname": hostname,
919 })
920 if err != nil {
921 return installer.ReleaseResources{}, err
922 }
923 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
924 go s.reconciler.Reconcile(ctx)
925 return rr, err
926 })
927 ch := make(chan error)
928 t.OnDone(func(err error) {
929 ch <- err
930 })
931 go t.Start()
932 err := <-ch
933 if err != nil {
934 return nil, err
935 }
936 for {
937 ip, err := s.vpnAPIClient.GetNodeIP(vpnUser, hostname)
938 if err == nil {
939 return ip, nil
940 }
941 if errors.Is(err, installer.ErrorNotFound) {
942 time.Sleep(5 * time.Second)
943 }
944 }
945 }
946}
gio8f290322024-09-21 15:37:45 +0400947
948func (s *AppManagerServer) setupRemoteClusterStorage() cluster.ClusterSetupFunc {
949 return func(cm cluster.Manager) error {
950 name := cm.State().Name
951 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
952 app, err := installer.FindEnvApp(s.fr, "longhorn")
953 if err != nil {
954 return installer.ReleaseResources{}, err
955 }
956 env, err := s.m.Config()
957 if err != nil {
958 return installer.ReleaseResources{}, err
959 }
960 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
961 appDir := fmt.Sprintf("/clusters/%s/storage", name)
962 namespace := fmt.Sprintf("%scluster-%s-storage", env.NamespacePrefix, name)
963 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
964 "cluster": name,
965 })
966 if err != nil {
967 return installer.ReleaseResources{}, err
968 }
969 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
970 go s.reconciler.Reconcile(ctx)
971 return rr, err
972 })
973 ch := make(chan error)
974 t.OnDone(func(err error) {
975 ch <- err
976 })
977 go t.Start()
978 err := <-ch
979 if err != nil {
980 return err
981 }
982 cm.EnableStorage()
983 return nil
984 }
985}