blob: 75d12b9648c24231eed81cffc2de4598f2b5c2bc [file] [log] [blame]
gio0eaf2712024-04-14 13:08:46 +04001package welcome
2
3import (
gio81246f02024-07-10 12:02:15 +04004 "context"
gio0eaf2712024-04-14 13:08:46 +04005 "encoding/json"
gio9d66f322024-07-06 13:45:10 +04006 "errors"
gio0eaf2712024-04-14 13:08:46 +04007 "fmt"
gio81246f02024-07-10 12:02:15 +04008 "golang.org/x/crypto/bcrypt"
gio0eaf2712024-04-14 13:08:46 +04009 "io"
gio9d66f322024-07-06 13:45:10 +040010 "io/fs"
gio0eaf2712024-04-14 13:08:46 +040011 "net/http"
12 "strings"
gio9d66f322024-07-06 13:45:10 +040013 "sync"
gio0eaf2712024-04-14 13:08:46 +040014
15 "github.com/giolekva/pcloud/core/installer"
16 "github.com/giolekva/pcloud/core/installer/soft"
gio33059762024-07-05 13:19:07 +040017
18 "github.com/gorilla/mux"
gio81246f02024-07-10 12:02:15 +040019 "github.com/gorilla/securecookie"
gio0eaf2712024-04-14 13:08:46 +040020)
21
gio9d66f322024-07-06 13:45:10 +040022const (
gioa60f0de2024-07-08 10:49:48 +040023 ConfigRepoName = "config"
gio9d66f322024-07-06 13:45:10 +040024 namespacesFile = "/namespaces.json"
gio81246f02024-07-10 12:02:15 +040025 loginPath = "/login"
26 logoutPath = "/logout"
27 sessionCookie = "dodo-app-session"
28 userCtx = "user"
gio9d66f322024-07-06 13:45:10 +040029)
30
gio0eaf2712024-04-14 13:08:46 +040031type DodoAppServer struct {
gio9d66f322024-07-06 13:45:10 +040032 l sync.Locker
gioa60f0de2024-07-08 10:49:48 +040033 st Store
gio33059762024-07-05 13:19:07 +040034 port int
gioa60f0de2024-07-08 10:49:48 +040035 apiPort int
gio33059762024-07-05 13:19:07 +040036 self string
37 sshKey string
38 gitRepoPublicKey string
39 client soft.Client
40 namespace string
41 env installer.EnvConfig
42 nsc installer.NamespaceCreator
43 jc installer.JobCreator
44 workers map[string]map[string]struct{}
45 appNs map[string]string
gio81246f02024-07-10 12:02:15 +040046 sc *securecookie.SecureCookie
gio0eaf2712024-04-14 13:08:46 +040047}
48
gio33059762024-07-05 13:19:07 +040049// TODO(gio): Initialize appNs on startup
gio0eaf2712024-04-14 13:08:46 +040050func NewDodoAppServer(
gioa60f0de2024-07-08 10:49:48 +040051 st Store,
gio0eaf2712024-04-14 13:08:46 +040052 port int,
gioa60f0de2024-07-08 10:49:48 +040053 apiPort int,
gio33059762024-07-05 13:19:07 +040054 self string,
gio0eaf2712024-04-14 13:08:46 +040055 sshKey string,
gio33059762024-07-05 13:19:07 +040056 gitRepoPublicKey string,
gio0eaf2712024-04-14 13:08:46 +040057 client soft.Client,
58 namespace string,
gio33059762024-07-05 13:19:07 +040059 nsc installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +040060 jc installer.JobCreator,
gio0eaf2712024-04-14 13:08:46 +040061 env installer.EnvConfig,
gio9d66f322024-07-06 13:45:10 +040062) (*DodoAppServer, error) {
gio81246f02024-07-10 12:02:15 +040063 sc := securecookie.New(
64 securecookie.GenerateRandomKey(64),
65 securecookie.GenerateRandomKey(32),
66 )
gio9d66f322024-07-06 13:45:10 +040067 s := &DodoAppServer{
68 &sync.Mutex{},
gioa60f0de2024-07-08 10:49:48 +040069 st,
gio0eaf2712024-04-14 13:08:46 +040070 port,
gioa60f0de2024-07-08 10:49:48 +040071 apiPort,
gio33059762024-07-05 13:19:07 +040072 self,
gio0eaf2712024-04-14 13:08:46 +040073 sshKey,
gio33059762024-07-05 13:19:07 +040074 gitRepoPublicKey,
gio0eaf2712024-04-14 13:08:46 +040075 client,
76 namespace,
77 env,
gio33059762024-07-05 13:19:07 +040078 nsc,
giof8843412024-05-22 16:38:05 +040079 jc,
gio266c04f2024-07-03 14:18:45 +040080 map[string]map[string]struct{}{},
gio33059762024-07-05 13:19:07 +040081 map[string]string{},
gio81246f02024-07-10 12:02:15 +040082 sc,
gio0eaf2712024-04-14 13:08:46 +040083 }
gioa60f0de2024-07-08 10:49:48 +040084 config, err := client.GetRepo(ConfigRepoName)
gio9d66f322024-07-06 13:45:10 +040085 if err != nil {
86 return nil, err
87 }
88 r, err := config.Reader(namespacesFile)
89 if err == nil {
90 defer r.Close()
91 if err := json.NewDecoder(r).Decode(&s.appNs); err != nil {
92 return nil, err
93 }
94 } else if !errors.Is(err, fs.ErrNotExist) {
95 return nil, err
96 }
97 return s, nil
gio0eaf2712024-04-14 13:08:46 +040098}
99
100func (s *DodoAppServer) Start() error {
gioa60f0de2024-07-08 10:49:48 +0400101 e := make(chan error)
102 go func() {
103 r := mux.NewRouter()
gio81246f02024-07-10 12:02:15 +0400104 r.Use(s.mwAuth)
105 r.HandleFunc(logoutPath, s.handleLogout).Methods(http.MethodGet)
106 r.HandleFunc("/{app-name}"+loginPath, s.handleLoginForm).Methods(http.MethodGet)
107 r.HandleFunc("/{app-name}"+loginPath, s.handleLogin).Methods(http.MethodPost)
108 r.HandleFunc("/{app-name}", s.handleAppStatus).Methods(http.MethodGet)
109 r.HandleFunc("/", s.handleStatus).Methods(http.MethodGet)
gioa60f0de2024-07-08 10:49:48 +0400110 e <- http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
111 }()
112 go func() {
113 r := mux.NewRouter()
gio81246f02024-07-10 12:02:15 +0400114 r.HandleFunc("/update", s.handleApiUpdate)
115 r.HandleFunc("/api/apps/{app-name}/workers", s.handleApiRegisterWorker).Methods(http.MethodPost)
116 r.HandleFunc("/api/apps", s.handleApiCreateApp).Methods(http.MethodPost)
117 r.HandleFunc("/api/add-admin-key", s.handleApiAddAdminKey).Methods(http.MethodPost)
gioa60f0de2024-07-08 10:49:48 +0400118 e <- http.ListenAndServe(fmt.Sprintf(":%d", s.apiPort), r)
119 }()
120 return <-e
121}
122
gio81246f02024-07-10 12:02:15 +0400123func (s *DodoAppServer) mwAuth(next http.Handler) http.Handler {
124 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
125 if strings.HasSuffix(r.URL.Path, loginPath) || strings.HasPrefix(r.URL.Path, logoutPath) {
126 next.ServeHTTP(w, r)
127 return
128 }
129 cookie, err := r.Cookie(sessionCookie)
130 if err != nil {
131 vars := mux.Vars(r)
132 appName, ok := vars["app-name"]
133 if !ok || appName == "" {
134 http.Error(w, "missing app-name", http.StatusBadRequest)
135 return
136 }
137 http.Redirect(w, r, fmt.Sprintf("/%s%s", appName, loginPath), http.StatusSeeOther)
138 return
139 }
140 var user string
141 if err := s.sc.Decode(sessionCookie, cookie.Value, &user); err != nil {
142 http.Error(w, "unauthorized", http.StatusUnauthorized)
143 return
144 }
145 next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), userCtx, user)))
146 })
147}
148
149func (s *DodoAppServer) handleLogout(w http.ResponseWriter, r *http.Request) {
150 http.SetCookie(w, &http.Cookie{
151 Name: sessionCookie,
152 Value: "",
153 Path: "/",
154 HttpOnly: true,
155 Secure: true,
156 })
157 http.Redirect(w, r, "/", http.StatusSeeOther)
158}
159
160func (s *DodoAppServer) handleLoginForm(w http.ResponseWriter, r *http.Request) {
161 vars := mux.Vars(r)
162 appName, ok := vars["app-name"]
163 if !ok || appName == "" {
164 http.Error(w, "missing app-name", http.StatusBadRequest)
165 return
166 }
167 fmt.Fprint(w, `
168<!DOCTYPE html>
169<html lang='en'>
170 <head>
171 <title>dodo: app - login</title>
172 <meta charset='utf-8'>
173 </head>
174 <body>
175 <form action="" method="POST">
176 <input type="password" placeholder="Password" name="password" required />
177 <button type="submit">Login</button>
178 </form>
179 </body>
180</html>
181`)
182}
183
184func (s *DodoAppServer) handleLogin(w http.ResponseWriter, r *http.Request) {
185 vars := mux.Vars(r)
186 appName, ok := vars["app-name"]
187 if !ok || appName == "" {
188 http.Error(w, "missing app-name", http.StatusBadRequest)
189 return
190 }
191 password := r.FormValue("password")
192 if password == "" {
193 http.Error(w, "missing password", http.StatusBadRequest)
194 return
195 }
196 user, err := s.st.GetAppOwner(appName)
197 if err != nil {
198 http.Error(w, err.Error(), http.StatusInternalServerError)
199 return
200 }
201 hashed, err := s.st.GetUserPassword(user)
202 if err != nil {
203 http.Error(w, err.Error(), http.StatusInternalServerError)
204 return
205 }
206 if err := bcrypt.CompareHashAndPassword(hashed, []byte(password)); err != nil {
207 http.Redirect(w, r, r.URL.Path, http.StatusSeeOther)
208 return
209 }
210 if encoded, err := s.sc.Encode(sessionCookie, user); err == nil {
211 cookie := &http.Cookie{
212 Name: sessionCookie,
213 Value: encoded,
214 Path: "/",
215 Secure: true,
216 HttpOnly: true,
217 }
218 http.SetCookie(w, cookie)
219 }
220 http.Redirect(w, r, fmt.Sprintf("/%s", appName), http.StatusSeeOther)
221}
222
gioa60f0de2024-07-08 10:49:48 +0400223func (s *DodoAppServer) handleStatus(w http.ResponseWriter, r *http.Request) {
gio81246f02024-07-10 12:02:15 +0400224 user := r.Context().Value(userCtx)
225 if user == nil {
226 http.Error(w, "unauthorized", http.StatusUnauthorized)
227 return
228 }
229 apps, err := s.st.GetUserApps(user.(string))
gioa60f0de2024-07-08 10:49:48 +0400230 if err != nil {
231 http.Error(w, err.Error(), http.StatusInternalServerError)
232 return
233 }
234 for _, a := range apps {
235 fmt.Fprintf(w, "%s\n", a)
236 }
237}
238
239func (s *DodoAppServer) handleAppStatus(w http.ResponseWriter, r *http.Request) {
240 vars := mux.Vars(r)
241 appName, ok := vars["app-name"]
242 if !ok || appName == "" {
243 http.Error(w, "missing app-name", http.StatusBadRequest)
244 return
245 }
246 commits, err := s.st.GetCommitHistory(appName)
247 if err != nil {
248 http.Error(w, err.Error(), http.StatusInternalServerError)
249 return
250 }
251 for _, c := range commits {
252 fmt.Fprintf(w, "%s %s\n", c.Hash, c.Message)
253 }
gio0eaf2712024-04-14 13:08:46 +0400254}
255
gio81246f02024-07-10 12:02:15 +0400256type apiUpdateReq struct {
gio266c04f2024-07-03 14:18:45 +0400257 Ref string `json:"ref"`
258 Repository struct {
259 Name string `json:"name"`
260 } `json:"repository"`
gioa60f0de2024-07-08 10:49:48 +0400261 After string `json:"after"`
gio0eaf2712024-04-14 13:08:46 +0400262}
263
gio81246f02024-07-10 12:02:15 +0400264func (s *DodoAppServer) handleApiUpdate(w http.ResponseWriter, r *http.Request) {
gio0eaf2712024-04-14 13:08:46 +0400265 fmt.Println("update")
gio81246f02024-07-10 12:02:15 +0400266 var req apiUpdateReq
gio0eaf2712024-04-14 13:08:46 +0400267 var contents strings.Builder
268 io.Copy(&contents, r.Body)
269 c := contents.String()
270 fmt.Println(c)
271 if err := json.NewDecoder(strings.NewReader(c)).Decode(&req); err != nil {
272 fmt.Println(err)
273 return
274 }
gioa60f0de2024-07-08 10:49:48 +0400275 if req.Ref != "refs/heads/master" || req.Repository.Name == ConfigRepoName {
gio0eaf2712024-04-14 13:08:46 +0400276 return
277 }
gioa60f0de2024-07-08 10:49:48 +0400278 // TODO(gio): Create commit record on app init as well
gio0eaf2712024-04-14 13:08:46 +0400279 go func() {
gio33059762024-07-05 13:19:07 +0400280 if err := s.updateDodoApp(req.Repository.Name, s.appNs[req.Repository.Name]); err != nil {
gioa60f0de2024-07-08 10:49:48 +0400281 if err := s.st.CreateCommit(req.Repository.Name, req.After, err.Error()); err != nil {
282 fmt.Printf("Error: %s\n", err.Error())
283 return
284 }
285 }
286 if err := s.st.CreateCommit(req.Repository.Name, req.After, "OK"); err != nil {
287 fmt.Printf("Error: %s\n", err.Error())
288 }
289 for addr, _ := range s.workers[req.Repository.Name] {
290 go func() {
291 // TODO(gio): make port configurable
292 http.Get(fmt.Sprintf("http://%s/update", addr))
293 }()
gio0eaf2712024-04-14 13:08:46 +0400294 }
295 }()
gio0eaf2712024-04-14 13:08:46 +0400296}
297
gio81246f02024-07-10 12:02:15 +0400298type apiRegisterWorkerReq struct {
gio0eaf2712024-04-14 13:08:46 +0400299 Address string `json:"address"`
300}
301
gio81246f02024-07-10 12:02:15 +0400302func (s *DodoAppServer) handleApiRegisterWorker(w http.ResponseWriter, r *http.Request) {
gioa60f0de2024-07-08 10:49:48 +0400303 vars := mux.Vars(r)
304 appName, ok := vars["app-name"]
305 if !ok || appName == "" {
306 http.Error(w, "missing app-name", http.StatusBadRequest)
307 return
308 }
gio81246f02024-07-10 12:02:15 +0400309 var req apiRegisterWorkerReq
gio0eaf2712024-04-14 13:08:46 +0400310 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
311 http.Error(w, err.Error(), http.StatusInternalServerError)
312 return
313 }
gioa60f0de2024-07-08 10:49:48 +0400314 if _, ok := s.workers[appName]; !ok {
315 s.workers[appName] = map[string]struct{}{}
gio266c04f2024-07-03 14:18:45 +0400316 }
gioa60f0de2024-07-08 10:49:48 +0400317 s.workers[appName][req.Address] = struct{}{}
gio0eaf2712024-04-14 13:08:46 +0400318}
319
gio81246f02024-07-10 12:02:15 +0400320type apiCreateAppReq struct {
gio33059762024-07-05 13:19:07 +0400321 AdminPublicKey string `json:"adminPublicKey"`
322}
323
gio81246f02024-07-10 12:02:15 +0400324type apiCreateAppResp struct {
325 AppName string `json:"appName"`
326 Password string `json:"password"`
gio33059762024-07-05 13:19:07 +0400327}
328
gio81246f02024-07-10 12:02:15 +0400329func (s *DodoAppServer) handleApiCreateApp(w http.ResponseWriter, r *http.Request) {
330 var req apiCreateAppReq
gio33059762024-07-05 13:19:07 +0400331 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
332 http.Error(w, err.Error(), http.StatusBadRequest)
333 return
334 }
335 g := installer.NewFixedLengthRandomNameGenerator(3)
336 appName, err := g.Generate()
337 if err != nil {
338 http.Error(w, err.Error(), http.StatusInternalServerError)
339 return
340 }
gio81246f02024-07-10 12:02:15 +0400341 password, err := s.CreateApp(appName, req.AdminPublicKey)
342 if err != nil {
gio33059762024-07-05 13:19:07 +0400343 http.Error(w, err.Error(), http.StatusInternalServerError)
344 return
345 }
gio81246f02024-07-10 12:02:15 +0400346 resp := apiCreateAppResp{
347 AppName: appName,
348 Password: password,
349 }
gio33059762024-07-05 13:19:07 +0400350 if err := json.NewEncoder(w).Encode(resp); err != nil {
351 http.Error(w, err.Error(), http.StatusInternalServerError)
352 return
353 }
354}
355
gio81246f02024-07-10 12:02:15 +0400356func (s *DodoAppServer) CreateApp(appName, adminPublicKey string) (string, error) {
gio9d66f322024-07-06 13:45:10 +0400357 s.l.Lock()
358 defer s.l.Unlock()
gio33059762024-07-05 13:19:07 +0400359 fmt.Printf("Creating app: %s\n", appName)
360 if ok, err := s.client.RepoExists(appName); err != nil {
gio81246f02024-07-10 12:02:15 +0400361 return "", err
gio33059762024-07-05 13:19:07 +0400362 } else if ok {
gio81246f02024-07-10 12:02:15 +0400363 return "", nil
gio33059762024-07-05 13:19:07 +0400364 }
gio81246f02024-07-10 12:02:15 +0400365 user, err := s.client.FindUser(adminPublicKey)
366 if err != nil {
367 return "", err
368 }
369 if user != "" {
370 if err := s.client.AddPublicKey(user, adminPublicKey); err != nil {
371 return "", err
372 }
373 } else {
374 user = appName
375 if err := s.client.AddUser(user, adminPublicKey); err != nil {
376 return "", err
377 }
378 }
379 password := generatePassword()
380 // TODO(gio): take admin password for initial application as input
381 if appName == "app" {
382 password = "app"
383 }
384 hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
385 if err != nil {
386 return "", err
387 }
388 if err := s.st.CreateUser(user, hashed); err != nil {
389 if !errors.Is(err, ErrorAlreadyExists) {
390 return "", err
391 } else {
392 password = ""
393 }
394 }
395 if err := s.st.CreateApp(appName, user); err != nil {
396 return "", err
gioa60f0de2024-07-08 10:49:48 +0400397 }
gio33059762024-07-05 13:19:07 +0400398 if err := s.client.AddRepository(appName); err != nil {
gio81246f02024-07-10 12:02:15 +0400399 return "", err
gio33059762024-07-05 13:19:07 +0400400 }
401 appRepo, err := s.client.GetRepo(appName)
402 if err != nil {
gio81246f02024-07-10 12:02:15 +0400403 return "", err
gio33059762024-07-05 13:19:07 +0400404 }
405 if err := InitRepo(appRepo); err != nil {
gio81246f02024-07-10 12:02:15 +0400406 return "", err
gio33059762024-07-05 13:19:07 +0400407 }
408 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
409 app, err := installer.FindEnvApp(apps, "dodo-app-instance")
410 if err != nil {
gio81246f02024-07-10 12:02:15 +0400411 return "", err
gio33059762024-07-05 13:19:07 +0400412 }
413 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
414 suffix, err := suffixGen.Generate()
415 if err != nil {
gio81246f02024-07-10 12:02:15 +0400416 return "", err
gio33059762024-07-05 13:19:07 +0400417 }
418 namespace := fmt.Sprintf("%s%s%s", s.env.NamespacePrefix, app.Namespace(), suffix)
419 s.appNs[appName] = namespace
420 if err := s.updateDodoApp(appName, namespace); err != nil {
gio81246f02024-07-10 12:02:15 +0400421 return "", err
gio33059762024-07-05 13:19:07 +0400422 }
gioa60f0de2024-07-08 10:49:48 +0400423 repo, err := s.client.GetRepo(ConfigRepoName)
gio33059762024-07-05 13:19:07 +0400424 if err != nil {
gio81246f02024-07-10 12:02:15 +0400425 return "", err
gio33059762024-07-05 13:19:07 +0400426 }
427 hf := installer.NewGitHelmFetcher()
428 m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, "/")
429 if err != nil {
gio81246f02024-07-10 12:02:15 +0400430 return "", err
gio33059762024-07-05 13:19:07 +0400431 }
gio9d66f322024-07-06 13:45:10 +0400432 if err := repo.Do(func(fs soft.RepoFS) (string, error) {
433 w, err := fs.Writer(namespacesFile)
434 if err != nil {
435 return "", err
436 }
437 defer w.Close()
438 if err := json.NewEncoder(w).Encode(s.appNs); err != nil {
439 return "", err
440 }
441 if _, err := m.Install(
442 app,
443 appName,
444 "/"+appName,
445 namespace,
446 map[string]any{
447 "repoAddr": s.client.GetRepoAddress(appName),
448 "repoHost": strings.Split(s.client.Address(), ":")[0],
449 "gitRepoPublicKey": s.gitRepoPublicKey,
450 },
451 installer.WithConfig(&s.env),
452 installer.WithNoPublish(),
453 installer.WithNoLock(),
454 ); err != nil {
455 return "", err
456 }
457 return fmt.Sprintf("Installed app: %s", appName), nil
458 }); err != nil {
gio81246f02024-07-10 12:02:15 +0400459 return "", err
gio33059762024-07-05 13:19:07 +0400460 }
461 cfg, err := m.FindInstance(appName)
462 if err != nil {
gio81246f02024-07-10 12:02:15 +0400463 return "", err
gio33059762024-07-05 13:19:07 +0400464 }
465 fluxKeys, ok := cfg.Input["fluxKeys"]
466 if !ok {
gio81246f02024-07-10 12:02:15 +0400467 return "", fmt.Errorf("Fluxcd keys not found")
gio33059762024-07-05 13:19:07 +0400468 }
469 fluxPublicKey, ok := fluxKeys.(map[string]any)["public"]
470 if !ok {
gio81246f02024-07-10 12:02:15 +0400471 return "", fmt.Errorf("Fluxcd keys not found")
gio33059762024-07-05 13:19:07 +0400472 }
473 if ok, err := s.client.UserExists("fluxcd"); err != nil {
gio81246f02024-07-10 12:02:15 +0400474 return "", err
gio33059762024-07-05 13:19:07 +0400475 } else if ok {
476 if err := s.client.AddPublicKey("fluxcd", fluxPublicKey.(string)); err != nil {
gio81246f02024-07-10 12:02:15 +0400477 return "", err
gio33059762024-07-05 13:19:07 +0400478 }
479 } else {
480 if err := s.client.AddUser("fluxcd", fluxPublicKey.(string)); err != nil {
gio81246f02024-07-10 12:02:15 +0400481 return "", err
gio33059762024-07-05 13:19:07 +0400482 }
483 }
484 if err := s.client.AddReadOnlyCollaborator(appName, "fluxcd"); err != nil {
gio81246f02024-07-10 12:02:15 +0400485 return "", err
gio33059762024-07-05 13:19:07 +0400486 }
487 if err := s.client.AddWebhook(appName, fmt.Sprintf("http://%s/update", s.self), "--active=true", "--events=push", "--content-type=json"); err != nil {
gio81246f02024-07-10 12:02:15 +0400488 return "", err
gio33059762024-07-05 13:19:07 +0400489 }
gio81246f02024-07-10 12:02:15 +0400490 if err := s.client.AddReadWriteCollaborator(appName, user); err != nil {
491 return "", err
gio33059762024-07-05 13:19:07 +0400492 }
gio81246f02024-07-10 12:02:15 +0400493 return password, nil
gio33059762024-07-05 13:19:07 +0400494}
495
gio81246f02024-07-10 12:02:15 +0400496type apiAddAdminKeyReq struct {
gio70be3e52024-06-26 18:27:19 +0400497 Public string `json:"public"`
498}
499
gio81246f02024-07-10 12:02:15 +0400500func (s *DodoAppServer) handleApiAddAdminKey(w http.ResponseWriter, r *http.Request) {
501 var req apiAddAdminKeyReq
gio70be3e52024-06-26 18:27:19 +0400502 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
503 http.Error(w, err.Error(), http.StatusBadRequest)
504 return
505 }
506 if err := s.client.AddPublicKey("admin", req.Public); err != nil {
507 http.Error(w, err.Error(), http.StatusInternalServerError)
508 return
509 }
510}
511
gio33059762024-07-05 13:19:07 +0400512func (s *DodoAppServer) updateDodoApp(name, namespace string) error {
513 repo, err := s.client.GetRepo(name)
gio0eaf2712024-04-14 13:08:46 +0400514 if err != nil {
515 return err
516 }
giof8843412024-05-22 16:38:05 +0400517 hf := installer.NewGitHelmFetcher()
gio33059762024-07-05 13:19:07 +0400518 m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, "/.dodo")
gio0eaf2712024-04-14 13:08:46 +0400519 if err != nil {
520 return err
521 }
522 appCfg, err := soft.ReadFile(repo, "app.cue")
gio0eaf2712024-04-14 13:08:46 +0400523 if err != nil {
524 return err
525 }
526 app, err := installer.NewDodoApp(appCfg)
527 if err != nil {
528 return err
529 }
giof8843412024-05-22 16:38:05 +0400530 lg := installer.GitRepositoryLocalChartGenerator{"app", namespace}
giof71a0832024-06-27 14:45:45 +0400531 if _, err := m.Install(
532 app,
533 "app",
534 "/.dodo/app",
535 namespace,
536 map[string]any{
gioa60f0de2024-07-08 10:49:48 +0400537 "repoAddr": repo.FullAddress(),
538 "managerAddr": fmt.Sprintf("http://%s", s.self),
539 "appId": name,
540 "sshPrivateKey": s.sshKey,
giof71a0832024-06-27 14:45:45 +0400541 },
gio33059762024-07-05 13:19:07 +0400542 installer.WithConfig(&s.env),
giof71a0832024-06-27 14:45:45 +0400543 installer.WithLocalChartGenerator(lg),
544 installer.WithBranch("dodo"),
545 installer.WithForce(),
546 ); err != nil {
gio0eaf2712024-04-14 13:08:46 +0400547 return err
548 }
549 return nil
550}
gio33059762024-07-05 13:19:07 +0400551
552const goMod = `module dodo.app
553
554go 1.18
555`
556
557const mainGo = `package main
558
559import (
560 "flag"
561 "fmt"
562 "log"
563 "net/http"
564)
565
566var port = flag.Int("port", 8080, "Port to listen on")
567
568func handler(w http.ResponseWriter, r *http.Request) {
569 fmt.Fprintln(w, "Hello from Dodo App!")
570}
571
572func main() {
573 flag.Parse()
574 http.HandleFunc("/", handler)
575 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
576}
577`
578
579const appCue = `app: {
580 type: "golang:1.22.0"
581 run: "main.go"
582 ingress: {
583 network: "Private" // or Public
584 subdomain: "testapp"
585 auth: enabled: false
586 }
587}
588`
589
590func InitRepo(repo soft.RepoIO) error {
591 return repo.Do(func(fs soft.RepoFS) (string, error) {
592 {
593 w, err := fs.Writer("go.mod")
594 if err != nil {
595 return "", err
596 }
597 defer w.Close()
598 fmt.Fprint(w, goMod)
599 }
600 {
601 w, err := fs.Writer("main.go")
602 if err != nil {
603 return "", err
604 }
605 defer w.Close()
606 fmt.Fprintf(w, "%s", mainGo)
607 }
608 {
609 w, err := fs.Writer("app.cue")
610 if err != nil {
611 return "", err
612 }
613 defer w.Close()
614 fmt.Fprint(w, appCue)
615 }
616 return "go web app template", nil
617 })
618}
gio81246f02024-07-10 12:02:15 +0400619
620func generatePassword() string {
621 return "foo"
622}