blob: b3a0883ce00c53944ec55db12272d8a5c29358c5 [file] [log] [blame]
gio59946282024-10-07 12:55:51 +04001package appmanager
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04002
3import (
giofc441e32024-11-11 16:26:14 +04004 "bytes"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +04005 "context"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04006 "embed"
7 "encoding/json"
giof6ad2982024-08-23 17:42:49 +04008 "errors"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04009 "fmt"
10 "html/template"
giof6ad2982024-08-23 17:42:49 +040011 "net"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040012 "net/http"
giofc441e32024-11-11 16:26:14 +040013 "path/filepath"
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"
gio59946282024-10-07 12:55:51 +040024 "github.com/giolekva/pcloud/core/installer/server"
giof6ad2982024-08-23 17:42:49 +040025 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +040026 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040027)
28
gio59946282024-10-07 12:55:51 +040029//go:embed templates/*
30var templates embed.FS
31
32//go:embed static/*
33var staticAssets embed.FS
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040034
giof6ad2982024-08-23 17:42:49 +040035type taskForward struct {
36 task tasks.Task
37 redirectTo string
gio8c876172024-10-05 12:25:13 +040038 id int
giof6ad2982024-08-23 17:42:49 +040039}
40
gio59946282024-10-07 12:55:51 +040041type Server struct {
giof6ad2982024-08-23 17:42:49 +040042 l sync.Locker
43 port int
44 repo soft.RepoIO
45 m *installer.AppManager
46 r installer.AppRepository
47 fr installer.AppRepository
48 reconciler *tasks.FixedReconciler
49 h installer.HelmReleaseMonitor
50 cnc installer.ClusterNetworkConfigurator
51 vpnAPIClient installer.VPNAPIClient
gio8c876172024-10-05 12:25:13 +040052 tasks map[string]*taskForward
giof6ad2982024-08-23 17:42:49 +040053 tmpl tmplts
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040054}
55
56type tmplts struct {
giof6ad2982024-08-23 17:42:49 +040057 index *template.Template
58 app *template.Template
59 allClusters *template.Template
60 cluster *template.Template
61 task *template.Template
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040062}
63
gio59946282024-10-07 12:55:51 +040064func parseTemplates(fs embed.FS) (tmplts, error) {
65 base, err := template.New("base.html").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(fs, "templates/base.html")
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040066 if err != nil {
67 return tmplts{}, err
68 }
69 parse := func(path string) (*template.Template, error) {
70 if b, err := base.Clone(); err != nil {
71 return nil, err
72 } else {
73 return b.ParseFS(fs, path)
74 }
75 }
gio59946282024-10-07 12:55:51 +040076 index, err := parse("templates/index.html")
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040077 if err != nil {
78 return tmplts{}, err
79 }
gio59946282024-10-07 12:55:51 +040080 app, err := parse("templates/app.html")
Davit Tabidze3ec24cf2024-05-22 14:06:02 +040081 if err != nil {
82 return tmplts{}, err
83 }
gio59946282024-10-07 12:55:51 +040084 allClusters, err := parse("templates/all-clusters.html")
giof6ad2982024-08-23 17:42:49 +040085 if err != nil {
86 return tmplts{}, err
87 }
gio59946282024-10-07 12:55:51 +040088 cluster, err := parse("templates/cluster.html")
giof6ad2982024-08-23 17:42:49 +040089 if err != nil {
90 return tmplts{}, err
91 }
gio59946282024-10-07 12:55:51 +040092 task, err := parse("templates/task.html")
giof6ad2982024-08-23 17:42:49 +040093 if err != nil {
94 return tmplts{}, err
95 }
96 return tmplts{index, app, allClusters, cluster, task}, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040097}
98
gio59946282024-10-07 12:55:51 +040099func NewServer(
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400100 port int,
giof6ad2982024-08-23 17:42:49 +0400101 repo soft.RepoIO,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400102 m *installer.AppManager,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400103 r installer.AppRepository,
giof6ad2982024-08-23 17:42:49 +0400104 fr installer.AppRepository,
gio43b0f422024-08-21 10:40:13 +0400105 reconciler *tasks.FixedReconciler,
gio778577f2024-04-29 09:44:38 +0400106 h installer.HelmReleaseMonitor,
giof6ad2982024-08-23 17:42:49 +0400107 cnc installer.ClusterNetworkConfigurator,
108 vpnAPIClient installer.VPNAPIClient,
gio59946282024-10-07 12:55:51 +0400109) (*Server, error) {
110 tmpl, err := parseTemplates(templates)
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400111 if err != nil {
112 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400113 }
gio59946282024-10-07 12:55:51 +0400114 return &Server{
giof6ad2982024-08-23 17:42:49 +0400115 l: &sync.Mutex{},
116 port: port,
117 repo: repo,
118 m: m,
119 r: r,
120 fr: fr,
121 reconciler: reconciler,
122 h: h,
123 cnc: cnc,
124 vpnAPIClient: vpnAPIClient,
gio8c876172024-10-05 12:25:13 +0400125 tasks: make(map[string]*taskForward),
giof6ad2982024-08-23 17:42:49 +0400126 tmpl: tmpl,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400127 }, nil
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400128}
129
gio59946282024-10-07 12:55:51 +0400130func (s *Server) Start() error {
gioaa0fcdb2024-06-10 22:19:25 +0400131 r := mux.NewRouter()
gio59946282024-10-07 12:55:51 +0400132 r.PathPrefix("/static/").Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
giocb34ad22024-07-11 08:01:13 +0400133 r.HandleFunc("/api/networks", s.handleNetworks).Methods(http.MethodGet)
giof15b9da2024-09-19 06:59:16 +0400134 r.HandleFunc("/api/clusters", s.handleClusters).Methods(http.MethodGet)
135 r.HandleFunc("/api/proxy/add", s.handleProxyAdd).Methods(http.MethodPost)
136 r.HandleFunc("/api/proxy/remove", s.handleProxyRemove).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400137 r.HandleFunc("/api/app-repo", s.handleAppRepo)
138 r.HandleFunc("/api/app/{slug}/install", s.handleAppInstall).Methods(http.MethodPost)
139 r.HandleFunc("/api/app/{slug}", s.handleApp).Methods(http.MethodGet)
140 r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
141 r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
142 r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
giofc441e32024-11-11 16:26:14 +0400143 r.HandleFunc("/api/dodo-app", s.handleDodoAppInstall).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400144 r.HandleFunc("/clusters/{cluster}/servers/{server}/remove", s.handleClusterRemoveServer).Methods(http.MethodPost)
145 r.HandleFunc("/clusters/{cluster}/servers", s.handleClusterAddServer).Methods(http.MethodPost)
146 r.HandleFunc("/clusters/{name}", s.handleCluster).Methods(http.MethodGet)
gio8f290322024-09-21 15:37:45 +0400147 r.HandleFunc("/clusters/{name}/setup-storage", s.handleClusterSetupStorage).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400148 r.HandleFunc("/clusters/{name}/remove", s.handleRemoveCluster).Methods(http.MethodPost)
149 r.HandleFunc("/clusters", s.handleAllClusters).Methods(http.MethodGet)
150 r.HandleFunc("/clusters", s.handleCreateCluster).Methods(http.MethodPost)
gioaa0fcdb2024-06-10 22:19:25 +0400151 r.HandleFunc("/app/{slug}", s.handleAppUI).Methods(http.MethodGet)
152 r.HandleFunc("/instance/{slug}", s.handleInstanceUI).Methods(http.MethodGet)
giof6ad2982024-08-23 17:42:49 +0400153 r.HandleFunc("/tasks/{slug}", s.handleTaskStatus).Methods(http.MethodGet)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400154 r.HandleFunc("/{pageType}", s.handleAppsList).Methods(http.MethodGet)
155 r.HandleFunc("/", s.handleAppsList).Methods(http.MethodGet)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400156 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
gioaa0fcdb2024-06-10 22:19:25 +0400157 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400158}
159
giofc441e32024-11-11 16:26:14 +0400160type dodoAppInstallReq struct {
161 Id string `json:"id"`
162 SSHPrivateKey string `json:"sshPrivateKey"`
163 Config map[string]any `json:"config"`
164}
165
166func (s *Server) handleDodoAppInstall(w http.ResponseWriter, r *http.Request) {
167 var req dodoAppInstallReq
168 // TODO(gio): validate that no internal fields are overridden by request
169 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
170 http.Error(w, err.Error(), http.StatusBadRequest)
171 return
172 }
173 clusters, err := s.m.GetClusters()
174 if err != nil {
175 http.Error(w, err.Error(), http.StatusInternalServerError)
176 return
177 }
178 req.Config["clusters"] = installer.ToAccessConfigs(clusters)
179 var cfg bytes.Buffer
180 if err := json.NewEncoder(&cfg).Encode(req.Config); err != nil {
181 http.Error(w, err.Error(), http.StatusInternalServerError)
182 return
183 }
184 app, err := installer.NewDodoApp(cfg.Bytes())
185 if err != nil {
186 http.Error(w, err.Error(), http.StatusBadRequest)
187 return
188 }
189 appDir := filepath.Join("/dodo-app", req.Id)
190 namespace := "dodo-app-test" // TODO(gio)
191 if _, err := s.m.Install(app, req.Id, appDir, namespace, map[string]any{
192 "managerAddr": "", // TODO(gio)
193 "appId": req.Id,
194 "sshPrivateKey": req.SSHPrivateKey,
195 }); err != nil {
196 http.Error(w, err.Error(), http.StatusInternalServerError)
197 return
198 }
199}
200
gio59946282024-10-07 12:55:51 +0400201func (s *Server) handleNetworks(w http.ResponseWriter, r *http.Request) {
giocb34ad22024-07-11 08:01:13 +0400202 env, err := s.m.Config()
203 if err != nil {
204 http.Error(w, err.Error(), http.StatusInternalServerError)
205 return
206 }
207 networks, err := s.m.CreateNetworks(env)
208 if err != nil {
209 http.Error(w, err.Error(), http.StatusInternalServerError)
210 return
211 }
212 if err := json.NewEncoder(w).Encode(networks); err != nil {
213 http.Error(w, err.Error(), http.StatusInternalServerError)
214 return
215 }
216}
217
gio59946282024-10-07 12:55:51 +0400218func (s *Server) handleClusters(w http.ResponseWriter, r *http.Request) {
giof15b9da2024-09-19 06:59:16 +0400219 clusters, err := s.m.GetClusters()
220 if err != nil {
221 http.Error(w, err.Error(), http.StatusInternalServerError)
222 return
223 }
224 if err := json.NewEncoder(w).Encode(installer.ToAccessConfigs(clusters)); err != nil {
225 http.Error(w, err.Error(), http.StatusInternalServerError)
226 return
227 }
228}
229
230type proxyPair struct {
231 From string `json:"from"`
232 To string `json:"to"`
233}
234
gio59946282024-10-07 12:55:51 +0400235func (s *Server) handleProxyAdd(w http.ResponseWriter, r *http.Request) {
giof15b9da2024-09-19 06:59:16 +0400236 var req proxyPair
237 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
238 http.Error(w, err.Error(), http.StatusBadRequest)
239 return
240 }
241 if err := s.cnc.AddProxy(req.From, req.To); err != nil {
242 http.Error(w, err.Error(), http.StatusInternalServerError)
243 return
244 }
245}
246
gio59946282024-10-07 12:55:51 +0400247func (s *Server) handleProxyRemove(w http.ResponseWriter, r *http.Request) {
giof15b9da2024-09-19 06:59:16 +0400248 var req proxyPair
249 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
250 http.Error(w, err.Error(), http.StatusBadRequest)
251 return
252 }
253 if err := s.cnc.RemoveProxy(req.From, req.To); err != nil {
254 http.Error(w, err.Error(), http.StatusInternalServerError)
255 return
256 }
257}
258
259type app struct {
260 Name string `json:"name"`
261 Icon template.HTML `json:"icon"`
262 ShortDescription string `json:"shortDescription"`
263 Slug string `json:"slug"`
264 Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
265}
266
gio59946282024-10-07 12:55:51 +0400267func (s *Server) handleAppRepo(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400268 all, err := s.r.GetAll()
269 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400270 http.Error(w, err.Error(), http.StatusInternalServerError)
271 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400272 }
273 resp := make([]app, len(all))
274 for i, a := range all {
gio44f621b2024-04-29 09:44:38 +0400275 resp[i] = app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400276 }
gioaa0fcdb2024-06-10 22:19:25 +0400277 w.Header().Set("Content-Type", "application/json")
278 if err := json.NewEncoder(w).Encode(resp); err != nil {
279 http.Error(w, err.Error(), http.StatusInternalServerError)
280 return
281 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400282}
283
gio59946282024-10-07 12:55:51 +0400284func (s *Server) handleApp(w http.ResponseWriter, r *http.Request) {
gioaa0fcdb2024-06-10 22:19:25 +0400285 slug, ok := mux.Vars(r)["slug"]
286 if !ok {
287 http.Error(w, "empty slug", http.StatusBadRequest)
288 return
289 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400290 a, err := s.r.Find(slug)
291 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400292 http.Error(w, err.Error(), http.StatusInternalServerError)
293 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400294 }
gio7fbd4ad2024-08-27 10:06:39 +0400295 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400296 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400297 http.Error(w, err.Error(), http.StatusInternalServerError)
298 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400299 }
gioaa0fcdb2024-06-10 22:19:25 +0400300 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances}
301 w.Header().Set("Content-Type", "application/json")
302 if err := json.NewEncoder(w).Encode(resp); err != nil {
303 http.Error(w, err.Error(), http.StatusInternalServerError)
304 return
305 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400306}
307
gio59946282024-10-07 12:55:51 +0400308func (s *Server) handleInstance(w http.ResponseWriter, r *http.Request) {
gioaa0fcdb2024-06-10 22:19:25 +0400309 slug, ok := mux.Vars(r)["slug"]
310 if !ok {
311 http.Error(w, "empty slug", http.StatusBadRequest)
312 return
313 }
gio7fbd4ad2024-08-27 10:06:39 +0400314 instance, err := s.m.GetInstance(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400315 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400316 http.Error(w, err.Error(), http.StatusInternalServerError)
317 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400318 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400319 a, err := s.r.Find(instance.AppId)
320 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400321 http.Error(w, err.Error(), http.StatusInternalServerError)
322 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400323 }
gioaa0fcdb2024-06-10 22:19:25 +0400324 resp := app{a.Name(), a.Icon(), a.Description(), a.Slug(), []installer.AppInstanceConfig{*instance}}
325 w.Header().Set("Content-Type", "application/json")
326 if err := json.NewEncoder(w).Encode(resp); err != nil {
327 http.Error(w, err.Error(), http.StatusInternalServerError)
328 return
329 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400330}
331
gio59946282024-10-07 12:55:51 +0400332func (s *Server) handleAppInstall(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400333 s.l.Lock()
334 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400335 slug, ok := mux.Vars(r)["slug"]
336 if !ok {
337 http.Error(w, "empty slug", http.StatusBadRequest)
338 return
339 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400340 var values map[string]any
gio8c876172024-10-05 12:25:13 +0400341 if err := json.NewDecoder(r.Body).Decode(&values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400342 http.Error(w, err.Error(), http.StatusInternalServerError)
343 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400344 }
gio3cdee592024-04-17 10:15:56 +0400345 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400346 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400347 http.Error(w, err.Error(), http.StatusInternalServerError)
348 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400349 }
gio3cdee592024-04-17 10:15:56 +0400350 env, err := s.m.Config()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400351 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400352 http.Error(w, err.Error(), http.StatusInternalServerError)
353 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400354 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400355 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
gio3af43942024-04-16 08:13:50 +0400356 suffix, err := suffixGen.Generate()
357 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400358 http.Error(w, err.Error(), http.StatusInternalServerError)
359 return
gio3af43942024-04-16 08:13:50 +0400360 }
gio44f621b2024-04-29 09:44:38 +0400361 instanceId := a.Slug() + suffix
gio3cdee592024-04-17 10:15:56 +0400362 appDir := fmt.Sprintf("/apps/%s", instanceId)
363 namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
gio1cd65152024-08-16 08:18:49 +0400364 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
gio43b0f422024-08-21 10:40:13 +0400365 rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
366 if err == nil {
367 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
368 go s.reconciler.Reconcile(ctx)
369 }
370 return rr, err
gio1cd65152024-08-16 08:18:49 +0400371 })
gio778577f2024-04-29 09:44:38 +0400372 if _, ok := s.tasks[instanceId]; ok {
373 panic("MUST NOT REACH!")
374 }
gio8c876172024-10-05 12:25:13 +0400375 s.tasks[instanceId] = &taskForward{t, fmt.Sprintf("/instance/%s", instanceId), 0}
376 t.OnDone(s.cleanTask(instanceId, 0))
gio778577f2024-04-29 09:44:38 +0400377 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400378 if _, err := fmt.Fprintf(w, "/tasks/%s", instanceId); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400379 http.Error(w, err.Error(), http.StatusInternalServerError)
380 return
381 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400382}
383
gio59946282024-10-07 12:55:51 +0400384func (s *Server) handleAppUpdate(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400385 s.l.Lock()
386 defer s.l.Unlock()
gioaa0fcdb2024-06-10 22:19:25 +0400387 slug, ok := mux.Vars(r)["slug"]
388 if !ok {
389 http.Error(w, "empty slug", http.StatusBadRequest)
390 return
391 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400392 var values map[string]any
gio8c876172024-10-05 12:25:13 +0400393 if err := json.NewDecoder(r.Body).Decode(&values); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400394 http.Error(w, err.Error(), http.StatusInternalServerError)
395 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400396 }
gio8c876172024-10-05 12:25:13 +0400397 tid := 0
398 if t, ok := s.tasks[slug]; ok {
399 if t.task != nil {
400 http.Error(w, "Update already in progress", http.StatusBadRequest)
401 return
402 }
403 tid = t.id + 1
gio778577f2024-04-29 09:44:38 +0400404 }
giof8843412024-05-22 16:38:05 +0400405 rr, err := s.m.Update(slug, values)
gio778577f2024-04-29 09:44:38 +0400406 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400407 http.Error(w, err.Error(), http.StatusInternalServerError)
408 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400409 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400410 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
411 go s.reconciler.Reconcile(ctx)
gio778577f2024-04-29 09:44:38 +0400412 t := tasks.NewMonitorRelease(s.h, rr)
gio8c876172024-10-05 12:25:13 +0400413 t.OnDone(s.cleanTask(slug, tid))
414 s.tasks[slug] = &taskForward{t, fmt.Sprintf("/instance/%s", slug), tid}
gio778577f2024-04-29 09:44:38 +0400415 go t.Start()
giof6ad2982024-08-23 17:42:49 +0400416 if _, err := fmt.Fprintf(w, "/tasks/%s", slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400417 http.Error(w, err.Error(), http.StatusInternalServerError)
418 return
419 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400420}
421
gio59946282024-10-07 12:55:51 +0400422func (s *Server) handleAppRemove(w http.ResponseWriter, r *http.Request) {
gioaa0fcdb2024-06-10 22:19:25 +0400423 slug, ok := mux.Vars(r)["slug"]
424 if !ok {
425 http.Error(w, "empty slug", http.StatusBadRequest)
426 return
427 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400428 if err := s.m.Remove(slug); err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400429 http.Error(w, err.Error(), http.StatusInternalServerError)
430 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400431 }
Giorgi Lekveishvilid2f3dca2023-12-20 09:31:30 +0400432 ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
433 go s.reconciler.Reconcile(ctx)
gioaa0fcdb2024-06-10 22:19:25 +0400434 if _, err := fmt.Fprint(w, "/"); err != nil {
435 http.Error(w, err.Error(), http.StatusInternalServerError)
436 return
437 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400438}
439
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400440type PageData struct {
Davit Tabidze780a0d02024-08-05 20:53:26 +0400441 Apps []app
442 CurrentPage string
443 SearchTarget string
444 SearchValue string
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400445}
446
gio59946282024-10-07 12:55:51 +0400447func (s *Server) handleAppsList(w http.ResponseWriter, r *http.Request) {
Davit Tabidze780a0d02024-08-05 20:53:26 +0400448 pageType := mux.Vars(r)["pageType"]
449 if pageType == "" {
450 pageType = "all"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400451 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400452 searchQuery := r.FormValue("query")
453 apps, err := s.r.Filter(searchQuery)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400454 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400455 http.Error(w, err.Error(), http.StatusInternalServerError)
456 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400457 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400458 resp := make([]app, 0)
Davit Tabidze780a0d02024-08-05 20:53:26 +0400459 for _, a := range apps {
gio7fbd4ad2024-08-27 10:06:39 +0400460 instances, err := s.m.GetAllAppInstances(a.Slug())
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400461 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400462 http.Error(w, err.Error(), http.StatusInternalServerError)
463 return
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400464 }
Davit Tabidze780a0d02024-08-05 20:53:26 +0400465 switch pageType {
466 case "installed":
467 if len(instances) != 0 {
468 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
469 }
470 case "not-installed":
471 if len(instances) == 0 {
472 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
473 }
474 default:
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400475 resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
476 }
477 }
478 data := PageData{
Davit Tabidze780a0d02024-08-05 20:53:26 +0400479 Apps: resp,
480 CurrentPage: pageType,
481 SearchTarget: pageType,
482 SearchValue: searchQuery,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400483 }
gioaa0fcdb2024-06-10 22:19:25 +0400484 if err := s.tmpl.index.Execute(w, data); err != nil {
485 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400486 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400487}
488
489type appPageData struct {
gio3cdee592024-04-17 10:15:56 +0400490 App installer.EnvApp
491 Instance *installer.AppInstanceConfig
492 Instances []installer.AppInstanceConfig
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400493 AvailableNetworks []installer.Network
giof6ad2982024-08-23 17:42:49 +0400494 AvailableClusters []cluster.State
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400495 CurrentPage string
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400496}
497
gio59946282024-10-07 12:55:51 +0400498func (s *Server) handleAppUI(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400499 global, err := s.m.Config()
500 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 }
gioaa0fcdb2024-06-10 22:19:25 +0400504 slug, ok := mux.Vars(r)["slug"]
505 if !ok {
506 http.Error(w, "empty slug", http.StatusBadRequest)
507 return
508 }
gio3cdee592024-04-17 10:15:56 +0400509 a, err := installer.FindEnvApp(s.r, slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400510 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400511 http.Error(w, err.Error(), http.StatusInternalServerError)
512 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400513 }
gio7fbd4ad2024-08-27 10:06:39 +0400514 instances, err := s.m.GetAllAppInstances(slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400515 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400516 http.Error(w, err.Error(), http.StatusInternalServerError)
517 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400518 }
giocb34ad22024-07-11 08:01:13 +0400519 networks, err := s.m.CreateNetworks(global)
520 if err != nil {
521 http.Error(w, err.Error(), http.StatusInternalServerError)
522 return
523 }
giof6ad2982024-08-23 17:42:49 +0400524 clusters, err := s.m.GetClusters()
525 if err != nil {
526 http.Error(w, err.Error(), http.StatusInternalServerError)
527 return
528 }
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400529 data := appPageData{
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400530 App: a,
531 Instances: instances,
giocb34ad22024-07-11 08:01:13 +0400532 AvailableNetworks: networks,
giof6ad2982024-08-23 17:42:49 +0400533 AvailableClusters: clusters,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400534 CurrentPage: a.Name(),
535 }
gioaa0fcdb2024-06-10 22:19:25 +0400536 if err := s.tmpl.app.Execute(w, data); err != nil {
537 http.Error(w, err.Error(), http.StatusInternalServerError)
538 return
539 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400540}
541
gio59946282024-10-07 12:55:51 +0400542func (s *Server) handleInstanceUI(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400543 s.l.Lock()
544 defer s.l.Unlock()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400545 global, err := s.m.Config()
546 if err != nil {
gioaa0fcdb2024-06-10 22:19:25 +0400547 http.Error(w, err.Error(), http.StatusInternalServerError)
548 return
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400549 }
gioaa0fcdb2024-06-10 22:19:25 +0400550 slug, ok := mux.Vars(r)["slug"]
551 if !ok {
552 http.Error(w, "empty slug", http.StatusBadRequest)
553 return
554 }
gio8c876172024-10-05 12:25:13 +0400555 if t, ok := s.tasks[slug]; ok && t.task != nil {
giof6ad2982024-08-23 17:42:49 +0400556 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", slug), http.StatusSeeOther)
557 return
558 }
gio8c876172024-10-05 12:25:13 +0400559 instance, err := s.m.GetInstance(slug)
560 if err != nil {
561 http.Error(w, err.Error(), http.StatusInternalServerError)
562 return
563 }
564 a, err := s.m.GetInstanceApp(instance.Id)
565 if err != nil {
566 http.Error(w, err.Error(), http.StatusInternalServerError)
567 return
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,
gio1cd65152024-08-16 08:18:49 +0400590 CurrentPage: slug,
Davit Tabidze3ec24cf2024-05-22 14:06:02 +0400591 }
gioaa0fcdb2024-06-10 22:19:25 +0400592 if err := s.tmpl.app.Execute(w, data); err != nil {
593 http.Error(w, err.Error(), http.StatusInternalServerError)
594 return
595 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400596}
giof6ad2982024-08-23 17:42:49 +0400597
598type taskStatusData struct {
599 CurrentPage string
600 Task tasks.Task
601}
602
gio59946282024-10-07 12:55:51 +0400603func (s *Server) handleTaskStatus(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400604 s.l.Lock()
605 defer s.l.Unlock()
606 slug, ok := mux.Vars(r)["slug"]
607 if !ok {
608 http.Error(w, "empty slug", http.StatusBadRequest)
609 return
610 }
611 t, ok := s.tasks[slug]
612 if !ok {
613 http.Error(w, "task not found", http.StatusInternalServerError)
614
615 return
616 }
gio8c876172024-10-05 12:25:13 +0400617 if ok && t.task == nil {
giof6ad2982024-08-23 17:42:49 +0400618 http.Redirect(w, r, t.redirectTo, http.StatusSeeOther)
619 return
620 }
621 data := taskStatusData{
622 CurrentPage: "",
623 Task: t.task,
624 }
625 if err := s.tmpl.task.Execute(w, data); err != nil {
626 http.Error(w, err.Error(), http.StatusInternalServerError)
627 return
628 }
629}
630
631type clustersData struct {
632 CurrentPage string
633 Clusters []cluster.State
634}
635
gio59946282024-10-07 12:55:51 +0400636func (s *Server) handleAllClusters(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400637 clusters, err := s.m.GetClusters()
638 if err != nil {
639 http.Error(w, err.Error(), http.StatusInternalServerError)
640 return
641 }
642 data := clustersData{
643 "clusters",
644 clusters,
645 }
646 if err := s.tmpl.allClusters.Execute(w, data); err != nil {
647 http.Error(w, err.Error(), http.StatusInternalServerError)
648 return
649 }
650}
651
652type clusterData struct {
653 CurrentPage string
654 Cluster cluster.State
655}
656
gio59946282024-10-07 12:55:51 +0400657func (s *Server) handleCluster(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400658 name, ok := mux.Vars(r)["name"]
659 if !ok {
660 http.Error(w, "empty name", http.StatusBadRequest)
661 return
662 }
663 m, err := s.getClusterManager(name)
664 if err != nil {
665 if errors.Is(err, installer.ErrorNotFound) {
666 http.Error(w, "not found", http.StatusNotFound)
667 } else {
668 http.Error(w, err.Error(), http.StatusInternalServerError)
669 }
670 return
671 }
672 data := clusterData{
673 "clusters",
674 m.State(),
675 }
676 if err := s.tmpl.cluster.Execute(w, data); err != nil {
677 http.Error(w, err.Error(), http.StatusInternalServerError)
678 return
679 }
680}
681
gio59946282024-10-07 12:55:51 +0400682func (s *Server) handleClusterSetupStorage(w http.ResponseWriter, r *http.Request) {
gio8f290322024-09-21 15:37:45 +0400683 cName, ok := mux.Vars(r)["name"]
684 if !ok {
685 http.Error(w, "empty name", http.StatusBadRequest)
686 return
687 }
gio8c876172024-10-05 12:25:13 +0400688 tid := 0
689 if t, ok := s.tasks[cName]; ok {
690 if t.task != nil {
691 http.Error(w, "cluster task in progress", http.StatusLocked)
692 return
693 }
694 tid = t.id + 1
gio8f290322024-09-21 15:37:45 +0400695 }
696 m, err := s.getClusterManager(cName)
697 if err != nil {
698 if errors.Is(err, installer.ErrorNotFound) {
699 http.Error(w, "not found", http.StatusNotFound)
700 } else {
701 http.Error(w, err.Error(), http.StatusInternalServerError)
702 }
703 return
704 }
705 task := tasks.NewClusterSetupTask(m, s.setupRemoteClusterStorage(), s.repo, fmt.Sprintf("cluster %s: setting up storage", m.State().Name))
gio8c876172024-10-05 12:25:13 +0400706 task.OnDone(s.cleanTask(cName, tid))
gio8f290322024-09-21 15:37:45 +0400707 go task.Start()
gio8c876172024-10-05 12:25:13 +0400708 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
gio8f290322024-09-21 15:37:45 +0400709 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
710}
711
gio59946282024-10-07 12:55:51 +0400712func (s *Server) handleClusterRemoveServer(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400713 s.l.Lock()
714 defer s.l.Unlock()
715 cName, ok := mux.Vars(r)["cluster"]
716 if !ok {
717 http.Error(w, "empty name", http.StatusBadRequest)
718 return
719 }
gio8c876172024-10-05 12:25:13 +0400720 tid := 0
721 if t, ok := s.tasks[cName]; ok {
722 if t.task != nil {
723 http.Error(w, "cluster task in progress", http.StatusLocked)
724 return
725 }
726 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400727 }
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)
gio8c876172024-10-05 12:25:13 +0400743 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400744 go task.Start()
gio8c876172024-10-05 12:25:13 +0400745 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400746 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
747}
748
gio59946282024-10-07 12:55:51 +0400749func (s *Server) getClusterManager(cName string) (cluster.Manager, error) {
giof6ad2982024-08-23 17:42:49 +0400750 clusters, err := s.m.GetClusters()
751 if err != nil {
752 return nil, err
753 }
754 var c *cluster.State
755 for _, i := range clusters {
756 if i.Name == cName {
757 c = &i
758 break
759 }
760 }
761 if c == nil {
762 return nil, installer.ErrorNotFound
763 }
764 return cluster.RestoreKubeManager(*c)
765}
766
gio59946282024-10-07 12:55:51 +0400767func (s *Server) handleClusterAddServer(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400768 s.l.Lock()
769 defer s.l.Unlock()
770 cName, ok := mux.Vars(r)["cluster"]
771 if !ok {
772 http.Error(w, "empty name", http.StatusBadRequest)
773 return
774 }
gio8c876172024-10-05 12:25:13 +0400775 tid := 0
776 if t, ok := s.tasks[cName]; ok {
777 if t.task != nil {
778 http.Error(w, "cluster task in progress", http.StatusLocked)
779 return
780 }
781 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400782 }
783 m, err := s.getClusterManager(cName)
784 if err != nil {
785 if errors.Is(err, installer.ErrorNotFound) {
786 http.Error(w, "not found", http.StatusNotFound)
787 } else {
788 http.Error(w, err.Error(), http.StatusInternalServerError)
789 }
790 return
791 }
792 t := r.PostFormValue("type")
gio8f290322024-09-21 15:37:45 +0400793 ip := net.ParseIP(strings.TrimSpace(r.PostFormValue("ip")))
giof6ad2982024-08-23 17:42:49 +0400794 if ip == nil {
795 http.Error(w, "invalid ip", http.StatusBadRequest)
796 return
797 }
798 port := 22
799 if p := r.PostFormValue("port"); p != "" {
800 port, err = strconv.Atoi(p)
801 if err != nil {
802 http.Error(w, err.Error(), http.StatusBadRequest)
803 return
804 }
805 }
806 server := cluster.Server{
807 IP: ip,
808 Port: port,
809 User: r.PostFormValue("user"),
810 Password: r.PostFormValue("password"),
811 }
812 var task tasks.Task
813 switch strings.ToLower(t) {
814 case "controller":
815 if len(m.State().Controllers) == 0 {
816 task = tasks.NewClusterInitTask(m, server, s.cnc, s.repo, s.setupRemoteCluster())
817 } else {
818 task = tasks.NewClusterJoinControllerTask(m, server, s.repo)
819 }
820 case "worker":
821 task = tasks.NewClusterJoinWorkerTask(m, server, s.repo)
822 default:
823 http.Error(w, "invalid type", http.StatusBadRequest)
824 return
825 }
gio8c876172024-10-05 12:25:13 +0400826 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400827 go task.Start()
gio8c876172024-10-05 12:25:13 +0400828 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400829 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
830}
831
gio59946282024-10-07 12:55:51 +0400832func (s *Server) handleCreateCluster(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400833 cName := r.PostFormValue("name")
834 if cName == "" {
835 http.Error(w, "no name", http.StatusBadRequest)
836 return
837 }
838 st := cluster.State{Name: cName}
839 if _, err := s.repo.Do(func(fs soft.RepoFS) (string, error) {
840 if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", cName), st); err != nil {
841 return "", err
842 }
843 return fmt.Sprintf("create cluster: %s", cName), nil
844 }); err != nil {
845 http.Error(w, err.Error(), http.StatusInternalServerError)
846 return
847 }
848 http.Redirect(w, r, fmt.Sprintf("/clusters/%s", cName), http.StatusSeeOther)
849}
850
gio59946282024-10-07 12:55:51 +0400851func (s *Server) handleRemoveCluster(w http.ResponseWriter, r *http.Request) {
giof6ad2982024-08-23 17:42:49 +0400852 cName, ok := mux.Vars(r)["name"]
853 if !ok {
854 http.Error(w, "empty name", http.StatusBadRequest)
855 return
856 }
gio8c876172024-10-05 12:25:13 +0400857 tid := 0
858 if t, ok := s.tasks[cName]; ok {
859 if t.task != nil {
860 http.Error(w, "cluster task in progress", http.StatusLocked)
861 return
862 }
863 tid = t.id + 1
giof6ad2982024-08-23 17:42:49 +0400864 }
865 m, err := s.getClusterManager(cName)
866 if err != nil {
867 if errors.Is(err, installer.ErrorNotFound) {
868 http.Error(w, "not found", http.StatusNotFound)
869 } else {
870 http.Error(w, err.Error(), http.StatusInternalServerError)
871 }
872 return
873 }
874 task := tasks.NewRemoveClusterTask(m, s.cnc, s.repo)
gio8c876172024-10-05 12:25:13 +0400875 task.OnDone(s.cleanTask(cName, tid))
giof6ad2982024-08-23 17:42:49 +0400876 go task.Start()
gio8c876172024-10-05 12:25:13 +0400877 s.tasks[cName] = &taskForward{task, fmt.Sprintf("/clusters/%s", cName), tid}
giof6ad2982024-08-23 17:42:49 +0400878 http.Redirect(w, r, fmt.Sprintf("/tasks/%s", cName), http.StatusSeeOther)
879}
880
gio59946282024-10-07 12:55:51 +0400881func (s *Server) setupRemoteCluster() cluster.ClusterIngressSetupFunc {
giof6ad2982024-08-23 17:42:49 +0400882 const vpnUser = "private-network-proxy"
883 return func(name, kubeconfig, ingressClassName string) (net.IP, error) {
884 hostname := fmt.Sprintf("cluster-%s", name)
885 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
886 app, err := installer.FindEnvApp(s.fr, "cluster-network")
887 if err != nil {
888 return installer.ReleaseResources{}, err
889 }
890 env, err := s.m.Config()
891 if err != nil {
892 return installer.ReleaseResources{}, err
893 }
894 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
895 appDir := fmt.Sprintf("/clusters/%s/ingress", name)
gio8f290322024-09-21 15:37:45 +0400896 namespace := fmt.Sprintf("%scluster-%s-network", env.NamespacePrefix, name)
giof6ad2982024-08-23 17:42:49 +0400897 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
898 "cluster": map[string]any{
899 "name": name,
900 "kubeconfig": kubeconfig,
901 "ingressClassName": ingressClassName,
902 },
903 // TODO(gio): remove hardcoded user
904 "vpnUser": vpnUser,
905 "vpnProxyHostname": hostname,
906 })
907 if err != nil {
908 return installer.ReleaseResources{}, err
909 }
910 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
911 go s.reconciler.Reconcile(ctx)
912 return rr, err
913 })
914 ch := make(chan error)
915 t.OnDone(func(err error) {
916 ch <- err
917 })
918 go t.Start()
919 err := <-ch
920 if err != nil {
921 return nil, err
922 }
923 for {
924 ip, err := s.vpnAPIClient.GetNodeIP(vpnUser, hostname)
925 if err == nil {
926 return ip, nil
927 }
928 if errors.Is(err, installer.ErrorNotFound) {
929 time.Sleep(5 * time.Second)
930 }
931 }
932 }
933}
gio8f290322024-09-21 15:37:45 +0400934
gio59946282024-10-07 12:55:51 +0400935func (s *Server) setupRemoteClusterStorage() cluster.ClusterSetupFunc {
gio8f290322024-09-21 15:37:45 +0400936 return func(cm cluster.Manager) error {
937 name := cm.State().Name
938 t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
939 app, err := installer.FindEnvApp(s.fr, "longhorn")
940 if err != nil {
941 return installer.ReleaseResources{}, err
942 }
943 env, err := s.m.Config()
944 if err != nil {
945 return installer.ReleaseResources{}, err
946 }
947 instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
948 appDir := fmt.Sprintf("/clusters/%s/storage", name)
949 namespace := fmt.Sprintf("%scluster-%s-storage", env.NamespacePrefix, name)
950 rr, err := s.m.Install(app, instanceId, appDir, namespace, map[string]any{
951 "cluster": name,
952 })
953 if err != nil {
954 return installer.ReleaseResources{}, err
955 }
956 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
957 go s.reconciler.Reconcile(ctx)
958 return rr, err
959 })
960 ch := make(chan error)
961 t.OnDone(func(err error) {
962 ch <- err
963 })
964 go t.Start()
965 err := <-ch
966 if err != nil {
967 return err
968 }
969 cm.EnableStorage()
970 return nil
971 }
972}
gio8c876172024-10-05 12:25:13 +0400973
gio59946282024-10-07 12:55:51 +0400974func (s *Server) cleanTask(name string, id int) func(error) {
gio8c876172024-10-05 12:25:13 +0400975 return func(err error) {
976 if err != nil {
977 fmt.Printf("Task %s failed: %s", name, err.Error())
978 }
979 s.l.Lock()
980 defer s.l.Unlock()
981 s.tasks[name].task = nil
982 go func() {
983 time.Sleep(30 * time.Second)
984 s.l.Lock()
985 defer s.l.Unlock()
986 if t, ok := s.tasks[name]; ok && t.id == id {
987 delete(s.tasks, name)
988 }
989 }()
990 }
991}