blob: 31264fe02ae657183c7a1a76c7946a30d276277f [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
gio69b84432024-10-06 17:46:05 +04004 "context"
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +04005 "embed"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04006 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04007 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04008 "fmt"
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +04009 "html/template"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040010 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040011 "io/fs"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040012 "net"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040013 "net/http"
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040014 "net/netip"
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +040015 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040016
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040017 "github.com/gomarkdown/markdown"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040018 "github.com/gorilla/mux"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040019
20 "github.com/giolekva/pcloud/core/installer"
gioe72b54f2024-04-22 10:44:41 +040021 "github.com/giolekva/pcloud/core/installer/dns"
22 phttp "github.com/giolekva/pcloud/core/installer/http"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040023 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040024 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040025)
26
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040027//go:embed env-manager-tmpl/*
28var tmpls embed.FS
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040029
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040030var tmplsParsed templates
31
32func init() {
33 if t, err := parseTemplates(tmpls); err != nil {
34 panic(err)
35 } else {
36 tmplsParsed = t
37 }
38}
39
40type templates struct {
41 form *template.Template
42 status *template.Template
43}
44
45func parseTemplates(fs embed.FS) (templates, error) {
46 base, err := template.ParseFS(fs, "env-manager-tmpl/base.html")
47 if err != nil {
48 return templates{}, err
49 }
50 parse := func(path string) (*template.Template, error) {
51 if b, err := base.Clone(); err != nil {
52 return nil, err
53 } else {
54 return b.ParseFS(fs, path)
55 }
56 }
57 form, err := parse("env-manager-tmpl/form.html")
58 if err != nil {
59 return templates{}, err
60 }
61 status, err := parse("env-manager-tmpl/status.html")
62 if err != nil {
63 return templates{}, err
64 }
65 return templates{form, status}, nil
66}
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040067
68type Status string
69
70const (
71 StatusActive Status = "ACTIVE"
72 StatusAccepted Status = "ACCEPTED"
73)
74
75// TODO(giolekva): add CreatedAt and ValidUntil
76type invitation struct {
77 Token string `json:"token"`
78 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040079}
80
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040081type EnvServer struct {
gio69b84432024-10-06 17:46:05 +040082 srv *http.Server
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040083 port int
gioe72b54f2024-04-22 10:44:41 +040084 ss soft.Client
85 repo soft.RepoIO
86 repoClient soft.ClientGetter
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040087 nsCreator installer.NamespaceCreator
giof8843412024-05-22 16:38:05 +040088 jc installer.JobCreator
89 hf installer.HelmFetcher
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +040090 dnsFetcher installer.ZoneStatusFetcher
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040091 nameGenerator installer.NameGenerator
gioe72b54f2024-04-22 10:44:41 +040092 httpClient phttp.Client
93 dnsClient dns.Client
giod9c398e2024-06-06 13:33:03 +040094 Tasks tasks.TaskManager
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040095 envInfo map[string]template.HTML
gioe72b54f2024-04-22 10:44:41 +040096 dns map[string]installer.EnvDNS
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040097 dnsPublished map[string]struct{}
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040098}
99
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400100func NewEnvServer(
101 port int,
gioe72b54f2024-04-22 10:44:41 +0400102 ss soft.Client,
103 repo soft.RepoIO,
104 repoClient soft.ClientGetter,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400105 nsCreator installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +0400106 jc installer.JobCreator,
107 hf installer.HelmFetcher,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400108 dnsFetcher installer.ZoneStatusFetcher,
109 nameGenerator installer.NameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400110 httpClient phttp.Client,
111 dnsClient dns.Client,
giod9c398e2024-06-06 13:33:03 +0400112 tm tasks.TaskManager,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400113) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400114 return &EnvServer{
gio69b84432024-10-06 17:46:05 +0400115 nil,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400116 port,
117 ss,
118 repo,
gioe72b54f2024-04-22 10:44:41 +0400119 repoClient,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400120 nsCreator,
giof8843412024-05-22 16:38:05 +0400121 jc,
122 hf,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400123 dnsFetcher,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400124 nameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400125 httpClient,
126 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400127 tm,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400128 make(map[string]template.HTML),
gioe72b54f2024-04-22 10:44:41 +0400129 make(map[string]installer.EnvDNS),
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400130 make(map[string]struct{}),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400131 }
132}
133
gio69b84432024-10-06 17:46:05 +0400134func (s *EnvServer) Start() error {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400135 r := mux.NewRouter()
gio1bf00802024-08-17 12:31:41 +0400136 r.PathPrefix("/stat/").Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400137 r.Path("/env/{key}").Methods("GET").HandlerFunc(s.monitorTask)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400138 r.Path("/env/{key}").Methods("POST").HandlerFunc(s.publishDNSRecords)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400139 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
140 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400141 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
gio69b84432024-10-06 17:46:05 +0400142 srv := &http.Server{
143 Addr: fmt.Sprintf(":%d", s.port),
144 Handler: r,
145 }
146 s.srv = srv
147 if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
148 return err
149 }
150 return nil
151}
152
153func (s *EnvServer) Stop() error {
154 return s.srv.Shutdown(context.Background())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400155}
156
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400157func (s *EnvServer) monitorTask(w http.ResponseWriter, r *http.Request) {
158 vars := mux.Vars(r)
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400159 key, ok := vars["key"]
160 if !ok {
161 http.Error(w, "Task key not provided", http.StatusBadRequest)
162 return
163 }
giod9c398e2024-06-06 13:33:03 +0400164 t, err := s.Tasks.Get(key)
165 if err != nil {
166 http.Error(w, err.Error(), http.StatusBadRequest)
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400167 return
168 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400169 dnsRecords := ""
170 if _, ok := s.dnsPublished[key]; !ok {
171 dnsRef, ok := s.dns[key]
172 if !ok {
173 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
174 return
175 }
gioe72b54f2024-04-22 10:44:41 +0400176 if records, err := s.dnsFetcher.Fetch(dnsRef.Address); err == nil {
177 dnsRecords = records
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400178 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400179 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400180 data := map[string]any{
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400181 "Root": t,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400182 "EnvInfo": s.envInfo[key],
183 "DNSRecords": dnsRecords,
184 }
185 if err := tmplsParsed.status.Execute(w, data); err != nil {
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400186 http.Error(w, err.Error(), http.StatusInternalServerError)
187 return
188 }
189}
190
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400191func (s *EnvServer) publishDNSRecords(w http.ResponseWriter, r *http.Request) {
192 vars := mux.Vars(r)
193 key, ok := vars["key"]
194 if !ok {
195 http.Error(w, "Task key not provided", http.StatusBadRequest)
196 return
197 }
198 dnsRef, ok := s.dns[key]
199 if !ok {
200 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
201 return
202 }
gioe72b54f2024-04-22 10:44:41 +0400203 records, err := s.dnsFetcher.Fetch(dnsRef.Address)
204 if err != nil {
205 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
206 return
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400207 }
208 r.ParseForm()
209 if apiToken, err := getFormValue(r.PostForm, "api-token"); err != nil {
210 http.Error(w, err.Error(), http.StatusBadRequest)
211 return
212 } else {
213 p := NewGandiUpdater(apiToken)
gioe72b54f2024-04-22 10:44:41 +0400214 zone := strings.Join(strings.Split(dnsRef.Zone, ".")[1:], ".") // TODO(gio): this is not gonna work with no subdomain case
215 if err := p.Update(zone, strings.Split(records, "\n")); err != nil {
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400216 http.Error(w, err.Error(), http.StatusInternalServerError)
217 return
218 }
219 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400220 s.envInfo[key] = "Successfully published DNS records, waiting to propagate."
221 s.dnsPublished[key] = struct{}{}
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400222 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400223}
224
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400225func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400226 if err := tmplsParsed.form.Execute(w, nil); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400227 http.Error(w, err.Error(), http.StatusInternalServerError)
228 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400229}
230
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400231func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
232 invitations, err := s.readInvitations()
233 if err != nil {
234 http.Error(w, err.Error(), http.StatusInternalServerError)
235 return
236 }
237 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
238 if err != nil {
239 http.Error(w, err.Error(), http.StatusInternalServerError)
240 return
241
242 }
243 invitations = append(invitations, invitation{token, StatusActive})
244 if err := s.writeInvitations(invitations); err != nil {
245 http.Error(w, err.Error(), http.StatusInternalServerError)
246 return
247 }
248 if _, err := w.Write([]byte("OK")); err != nil {
249 http.Error(w, err.Error(), http.StatusInternalServerError)
250 return
251 }
252}
253
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400254type createEnvReq struct {
gio7841f4f2024-07-26 19:53:49 +0400255 Name string
256 ContactEmail string `json:"contactEmail"`
257 Domain string `json:"domain"`
258 PrivateNetworkSubdomain string `json:"privateNetworkSubdomain"`
259 AdminPublicKey string `json:"adminPublicKey"`
260 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400261}
262
263func (s *EnvServer) readInvitations() ([]invitation, error) {
264 r, err := s.repo.Reader("invitations")
265 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400266 if errors.Is(err, fs.ErrNotExist) {
267 return make([]invitation, 0), nil
268 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400269 return nil, err
270 }
271 defer r.Close()
272 dec := json.NewDecoder(r)
273 invitations := make([]invitation, 0)
274 for {
275 var i invitation
276 if err := dec.Decode(&i); err == io.EOF {
277 break
278 }
279 invitations = append(invitations, i)
280 }
281 return invitations, nil
282}
283
284func (s *EnvServer) writeInvitations(invitations []invitation) error {
285 w, err := s.repo.Writer("invitations")
286 if err != nil {
287 return err
288 }
289 defer w.Close()
290 enc := json.NewEncoder(w)
291 for _, i := range invitations {
292 if err := enc.Encode(i); err != nil {
293 return err
294 }
295 }
giob4a3a192024-08-19 09:55:47 +0400296 _, err = s.repo.CommitAndPush("Generated new invitation")
297 return err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400298}
299
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400300func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400301 var req createEnvReq
302 if err := func() error {
303 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400304 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400305 return err
306 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400307 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400308 return err
309 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400310 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400311 return err
312 }
gio7841f4f2024-07-26 19:53:49 +0400313 if req.PrivateNetworkSubdomain, err = getFormValue(r.PostForm, "private-network-subdomain"); err != nil {
314 return err
315 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400316 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400317 return err
318 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400319 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
320 return err
321 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400322 return nil
323 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400324 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400325 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400326 }
327 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400328 return req, nil
329}
330
331func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400332 invitations, err := s.readInvitations()
333 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400334 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400335 }
336 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400337 for i := range invitations {
338 if invitations[i].Token == token && invitations[i].Status == StatusActive {
339 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400340 found = true
341 break
342 }
343 }
344 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400345 return fmt.Errorf("Invitation not found")
346 }
347 return s.writeInvitations(invitations)
348}
349
350func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
gio1591fa72024-05-24 18:01:58 +0400351 if err := s.repo.Pull(); err != nil {
352 http.Error(w, err.Error(), http.StatusInternalServerError)
353 return
354 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400355 req, err := extractRequest(r)
356 if err != nil {
357 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400358 return
359 }
giof8843412024-05-22 16:38:05 +0400360 hf := installer.NewGitHelmFetcher()
361 lg := installer.NewInfraLocalChartGenerator()
362 mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator, hf, lg)
gio3cdee592024-04-17 10:15:56 +0400363 if err != nil {
364 http.Error(w, err.Error(), http.StatusInternalServerError)
365 return
366 }
367 var infra installer.InfraConfig
gioe72b54f2024-04-22 10:44:41 +0400368 if err := soft.ReadYaml(s.repo, "config.yaml", &infra); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400369 http.Error(w, err.Error(), http.StatusInternalServerError)
370 return
371 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400372 // if err := s.acceptInvitation(req.SecretToken); err != nil {
373 // http.Error(w, err.Error(), http.StatusInternalServerError)
374 // return
375 // }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400376 if name, err := s.nameGenerator.Generate(); err != nil {
377 http.Error(w, err.Error(), http.StatusInternalServerError)
378 return
379 } else {
380 req.Name = name
381 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400382 var cidrs installer.EnvCIDRs
gioe72b54f2024-04-22 10:44:41 +0400383 if err := soft.ReadYaml(s.repo, "env-cidrs.yaml", &cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400384 http.Error(w, err.Error(), http.StatusInternalServerError)
385 return
386 }
387 startIP, err := findNextStartIP(cidrs)
388 if err != nil {
389 http.Error(w, err.Error(), http.StatusInternalServerError)
390 return
391 }
392 cidrs = append(cidrs, installer.EnvCIDR{req.Name, startIP})
gioe72b54f2024-04-22 10:44:41 +0400393 if err := soft.WriteYaml(s.repo, "env-cidrs.yaml", cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400394 http.Error(w, err.Error(), http.StatusInternalServerError)
395 return
396 }
giob4a3a192024-08-19 09:55:47 +0400397 if _, err := s.repo.CommitAndPush(fmt.Sprintf("Allocate CIDR for %s", req.Name)); err != nil {
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400398 http.Error(w, err.Error(), http.StatusInternalServerError)
399 return
400 }
gioe72b54f2024-04-22 10:44:41 +0400401 envNetwork, err := installer.NewEnvNetwork(startIP)
402 if err != nil {
403 http.Error(w, err.Error(), http.StatusInternalServerError)
404 return
405 }
gio7841f4f2024-07-26 19:53:49 +0400406 privateDomain := ""
407 if req.PrivateNetworkSubdomain != "" {
408 privateDomain = fmt.Sprintf("%s.%s", req.PrivateNetworkSubdomain, req.Domain)
409 }
gioe72b54f2024-04-22 10:44:41 +0400410 env := installer.EnvConfig{
411 Id: req.Name,
412 InfraName: infra.Name,
413 Domain: req.Domain,
gio7841f4f2024-07-26 19:53:49 +0400414 PrivateDomain: privateDomain,
gioe72b54f2024-04-22 10:44:41 +0400415 ContactEmail: req.ContactEmail,
416 AdminPublicKey: req.AdminPublicKey,
417 PublicIP: infra.PublicIP,
418 NameserverIP: infra.PublicIP,
419 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
420 Network: envNetwork,
421 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400422 key := func() string {
423 for {
424 key, err := s.nameGenerator.Generate()
425 if err == nil {
426 return key
427 }
428 }
429 }()
430 infoUpdater := func(info string) {
431 s.envInfo[key] = template.HTML(markdown.ToHTML([]byte(info), nil, nil))
432 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400433 t, dns := tasks.NewCreateEnvTask(
gioe72b54f2024-04-22 10:44:41 +0400434 env,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400435 s.nsCreator,
giof8843412024-05-22 16:38:05 +0400436 s.jc,
437 s.hf,
gioe72b54f2024-04-22 10:44:41 +0400438 s.dnsFetcher,
439 s.httpClient,
440 s.dnsClient,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400441 s.repo,
gioe72b54f2024-04-22 10:44:41 +0400442 s.repoClient,
gio3cdee592024-04-17 10:15:56 +0400443 mgr,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400444 infoUpdater,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400445 )
giod9c398e2024-06-06 13:33:03 +0400446 if err := s.Tasks.Add(key, t); err != nil {
447 panic(err)
448 }
449
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400450 s.dns[key] = dns
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400451 go t.Start()
Giorgi Lekveishvilic85504d2023-12-20 19:29:47 +0400452 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400453}
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400454
455func findNextStartIP(cidrs installer.EnvCIDRs) (net.IP, error) {
456 m, err := netip.ParseAddr("10.0.0.0")
457 if err != nil {
458 return nil, err
459 }
460 for _, cidr := range cidrs {
461 i, err := netip.ParseAddr(cidr.IP.String())
462 if err != nil {
463 return nil, err
464 }
465 if i.Compare(m) > 0 {
466 m = i
467 }
468 }
469 sl := m.AsSlice()
470 sl[2]++
471 if sl[2] == 0b11111111 {
472 sl[2] = 0
473 sl[1]++
474 }
475 if sl[1] == 0b11111111 {
476 return nil, fmt.Errorf("Can not allocate")
477 }
478 ret, ok := netip.AddrFromSlice(sl)
479 if !ok {
480 return nil, fmt.Errorf("Must not reach")
481 }
482 return net.ParseIP(ret.String()), nil
483}