blob: d6e9eac31b2d611620febe1727190169b7f83053 [file] [log] [blame]
gio59946282024-10-07 12:55:51 +04001package env
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04002
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"
gio59946282024-10-07 12:55:51 +040023 "github.com/giolekva/pcloud/core/installer/server"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040024 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040025 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040026)
27
gio59946282024-10-07 12:55:51 +040028//go:embed templates/*
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040029var tmpls embed.FS
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040030
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040031var tmplsParsed templates
32
gio59946282024-10-07 12:55:51 +040033//go:embed static/*
34var staticAssets embed.FS
35
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040036func init() {
37 if t, err := parseTemplates(tmpls); err != nil {
38 panic(err)
39 } else {
40 tmplsParsed = t
41 }
42}
43
44type templates struct {
45 form *template.Template
46 status *template.Template
47}
48
49func parseTemplates(fs embed.FS) (templates, error) {
gio59946282024-10-07 12:55:51 +040050 base, err := template.ParseFS(fs, "templates/base.html")
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040051 if err != nil {
52 return templates{}, err
53 }
54 parse := func(path string) (*template.Template, error) {
55 if b, err := base.Clone(); err != nil {
56 return nil, err
57 } else {
58 return b.ParseFS(fs, path)
59 }
60 }
gio59946282024-10-07 12:55:51 +040061 form, err := parse("templates/form.html")
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040062 if err != nil {
63 return templates{}, err
64 }
gio59946282024-10-07 12:55:51 +040065 status, err := parse("templates/status.html")
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040066 if err != nil {
67 return templates{}, err
68 }
69 return templates{form, status}, nil
70}
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040071
72type Status string
73
74const (
75 StatusActive Status = "ACTIVE"
76 StatusAccepted Status = "ACCEPTED"
77)
78
79// TODO(giolekva): add CreatedAt and ValidUntil
80type invitation struct {
81 Token string `json:"token"`
82 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040083}
84
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040085type EnvServer struct {
gio69b84432024-10-06 17:46:05 +040086 srv *http.Server
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040087 port int
gioe72b54f2024-04-22 10:44:41 +040088 ss soft.Client
89 repo soft.RepoIO
90 repoClient soft.ClientGetter
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040091 nsCreator installer.NamespaceCreator
giof8843412024-05-22 16:38:05 +040092 jc installer.JobCreator
93 hf installer.HelmFetcher
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +040094 dnsFetcher installer.ZoneStatusFetcher
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040095 nameGenerator installer.NameGenerator
gioe72b54f2024-04-22 10:44:41 +040096 httpClient phttp.Client
97 dnsClient dns.Client
giod9c398e2024-06-06 13:33:03 +040098 Tasks tasks.TaskManager
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040099 envInfo map[string]template.HTML
gioe72b54f2024-04-22 10:44:41 +0400100 dns map[string]installer.EnvDNS
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400101 dnsPublished map[string]struct{}
giod6dc3122026-07-22 10:47:50 +0400102 allowNewEnvs bool
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400103}
104
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400105func NewEnvServer(
106 port int,
gioe72b54f2024-04-22 10:44:41 +0400107 ss soft.Client,
108 repo soft.RepoIO,
109 repoClient soft.ClientGetter,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400110 nsCreator installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +0400111 jc installer.JobCreator,
112 hf installer.HelmFetcher,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400113 dnsFetcher installer.ZoneStatusFetcher,
114 nameGenerator installer.NameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400115 httpClient phttp.Client,
116 dnsClient dns.Client,
giod9c398e2024-06-06 13:33:03 +0400117 tm tasks.TaskManager,
giod6dc3122026-07-22 10:47:50 +0400118 allowNewEnvs bool,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400119) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400120 return &EnvServer{
gio69b84432024-10-06 17:46:05 +0400121 nil,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400122 port,
123 ss,
124 repo,
gioe72b54f2024-04-22 10:44:41 +0400125 repoClient,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400126 nsCreator,
giof8843412024-05-22 16:38:05 +0400127 jc,
128 hf,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400129 dnsFetcher,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400130 nameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400131 httpClient,
132 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400133 tm,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400134 make(map[string]template.HTML),
gioe72b54f2024-04-22 10:44:41 +0400135 make(map[string]installer.EnvDNS),
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400136 make(map[string]struct{}),
giod6dc3122026-07-22 10:47:50 +0400137 allowNewEnvs,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400138 }
139}
140
gio69b84432024-10-06 17:46:05 +0400141func (s *EnvServer) Start() error {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400142 r := mux.NewRouter()
gio59946282024-10-07 12:55:51 +0400143 r.PathPrefix("/static/").Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400144 r.Path("/env/{key}").Methods("GET").HandlerFunc(s.monitorTask)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400145 r.Path("/env/{key}").Methods("POST").HandlerFunc(s.publishDNSRecords)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400146 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
147 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400148 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
gio69b84432024-10-06 17:46:05 +0400149 srv := &http.Server{
150 Addr: fmt.Sprintf(":%d", s.port),
151 Handler: r,
152 }
153 s.srv = srv
154 if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
155 return err
156 }
157 return nil
158}
159
160func (s *EnvServer) Stop() error {
161 return s.srv.Shutdown(context.Background())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400162}
163
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400164func (s *EnvServer) monitorTask(w http.ResponseWriter, r *http.Request) {
165 vars := mux.Vars(r)
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400166 key, ok := vars["key"]
167 if !ok {
168 http.Error(w, "Task key not provided", http.StatusBadRequest)
169 return
170 }
giod9c398e2024-06-06 13:33:03 +0400171 t, err := s.Tasks.Get(key)
172 if err != nil {
173 http.Error(w, err.Error(), http.StatusBadRequest)
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400174 return
175 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400176 dnsRecords := ""
177 if _, ok := s.dnsPublished[key]; !ok {
178 dnsRef, ok := s.dns[key]
179 if !ok {
180 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
181 return
182 }
gioe72b54f2024-04-22 10:44:41 +0400183 if records, err := s.dnsFetcher.Fetch(dnsRef.Address); err == nil {
184 dnsRecords = records
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400185 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400186 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400187 data := map[string]any{
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400188 "Root": t,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400189 "EnvInfo": s.envInfo[key],
190 "DNSRecords": dnsRecords,
191 }
192 if err := tmplsParsed.status.Execute(w, data); err != nil {
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400193 http.Error(w, err.Error(), http.StatusInternalServerError)
194 return
195 }
196}
197
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400198func (s *EnvServer) publishDNSRecords(w http.ResponseWriter, r *http.Request) {
199 vars := mux.Vars(r)
200 key, ok := vars["key"]
201 if !ok {
202 http.Error(w, "Task key not provided", http.StatusBadRequest)
203 return
204 }
205 dnsRef, ok := s.dns[key]
206 if !ok {
207 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
208 return
209 }
gioe72b54f2024-04-22 10:44:41 +0400210 records, err := s.dnsFetcher.Fetch(dnsRef.Address)
211 if err != nil {
212 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
213 return
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400214 }
215 r.ParseForm()
gio59946282024-10-07 12:55:51 +0400216 if apiToken, err := server.GetFormValue(r.PostForm, "api-token"); err != nil {
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400217 http.Error(w, err.Error(), http.StatusBadRequest)
218 return
219 } else {
220 p := NewGandiUpdater(apiToken)
gioe72b54f2024-04-22 10:44:41 +0400221 zone := strings.Join(strings.Split(dnsRef.Zone, ".")[1:], ".") // TODO(gio): this is not gonna work with no subdomain case
222 if err := p.Update(zone, strings.Split(records, "\n")); err != nil {
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400223 http.Error(w, err.Error(), http.StatusInternalServerError)
224 return
225 }
226 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400227 s.envInfo[key] = "Successfully published DNS records, waiting to propagate."
228 s.dnsPublished[key] = struct{}{}
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400229 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400230}
231
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400232func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400233 if err := tmplsParsed.form.Execute(w, nil); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400234 http.Error(w, err.Error(), http.StatusInternalServerError)
235 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400236}
237
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400238func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
239 invitations, err := s.readInvitations()
240 if err != nil {
241 http.Error(w, err.Error(), http.StatusInternalServerError)
242 return
243 }
244 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
245 if err != nil {
246 http.Error(w, err.Error(), http.StatusInternalServerError)
247 return
248
249 }
250 invitations = append(invitations, invitation{token, StatusActive})
251 if err := s.writeInvitations(invitations); err != nil {
252 http.Error(w, err.Error(), http.StatusInternalServerError)
253 return
254 }
255 if _, err := w.Write([]byte("OK")); err != nil {
256 http.Error(w, err.Error(), http.StatusInternalServerError)
257 return
258 }
259}
260
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400261type createEnvReq struct {
gio7841f4f2024-07-26 19:53:49 +0400262 Name string
263 ContactEmail string `json:"contactEmail"`
264 Domain string `json:"domain"`
265 PrivateNetworkSubdomain string `json:"privateNetworkSubdomain"`
266 AdminPublicKey string `json:"adminPublicKey"`
267 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400268}
269
270func (s *EnvServer) readInvitations() ([]invitation, error) {
271 r, err := s.repo.Reader("invitations")
272 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400273 if errors.Is(err, fs.ErrNotExist) {
274 return make([]invitation, 0), nil
275 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400276 return nil, err
277 }
278 defer r.Close()
279 dec := json.NewDecoder(r)
280 invitations := make([]invitation, 0)
281 for {
282 var i invitation
283 if err := dec.Decode(&i); err == io.EOF {
284 break
285 }
286 invitations = append(invitations, i)
287 }
288 return invitations, nil
289}
290
291func (s *EnvServer) writeInvitations(invitations []invitation) error {
292 w, err := s.repo.Writer("invitations")
293 if err != nil {
294 return err
295 }
296 defer w.Close()
297 enc := json.NewEncoder(w)
298 for _, i := range invitations {
299 if err := enc.Encode(i); err != nil {
300 return err
301 }
302 }
giob4a3a192024-08-19 09:55:47 +0400303 _, err = s.repo.CommitAndPush("Generated new invitation")
304 return err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400305}
306
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400307func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400308 var req createEnvReq
309 if err := func() error {
310 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400311 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400312 return err
313 }
gio59946282024-10-07 12:55:51 +0400314 if req.SecretToken, err = server.GetFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400315 return err
316 }
gio59946282024-10-07 12:55:51 +0400317 if req.Domain, err = server.GetFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400318 return err
319 }
gio59946282024-10-07 12:55:51 +0400320 if req.PrivateNetworkSubdomain, err = server.GetFormValue(r.PostForm, "private-network-subdomain"); err != nil {
gio7841f4f2024-07-26 19:53:49 +0400321 return err
322 }
gio59946282024-10-07 12:55:51 +0400323 if req.ContactEmail, err = server.GetFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400324 return err
325 }
gio59946282024-10-07 12:55:51 +0400326 if req.AdminPublicKey, err = server.GetFormValue(r.PostForm, "admin-public-key"); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400327 return err
328 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400329 return nil
330 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400331 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400332 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400333 }
334 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400335 return req, nil
336}
337
338func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400339 invitations, err := s.readInvitations()
340 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400341 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400342 }
343 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400344 for i := range invitations {
345 if invitations[i].Token == token && invitations[i].Status == StatusActive {
346 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400347 found = true
348 break
349 }
350 }
351 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400352 return fmt.Errorf("Invitation not found")
353 }
354 return s.writeInvitations(invitations)
355}
356
357func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
gio1591fa72024-05-24 18:01:58 +0400358 if err := s.repo.Pull(); err != nil {
359 http.Error(w, err.Error(), http.StatusInternalServerError)
360 return
361 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400362 req, err := extractRequest(r)
363 if err != nil {
364 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400365 return
366 }
giod6dc3122026-07-22 10:47:50 +0400367 // TODO(gio): remove
368 fmt.Printf("### NEW ENV REQUEST: %+v -- %+v\n", req, r.Header)
369 if !s.allowNewEnvs {
370 http.Error(w, "Domain is already taken", http.StatusBadRequest)
371 return
372 }
giof8843412024-05-22 16:38:05 +0400373 hf := installer.NewGitHelmFetcher()
374 lg := installer.NewInfraLocalChartGenerator()
375 mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator, hf, lg)
gio3cdee592024-04-17 10:15:56 +0400376 if err != nil {
377 http.Error(w, err.Error(), http.StatusInternalServerError)
378 return
379 }
380 var infra installer.InfraConfig
gioe72b54f2024-04-22 10:44:41 +0400381 if err := soft.ReadYaml(s.repo, "config.yaml", &infra); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400382 http.Error(w, err.Error(), http.StatusInternalServerError)
383 return
384 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400385 // if err := s.acceptInvitation(req.SecretToken); err != nil {
386 // http.Error(w, err.Error(), http.StatusInternalServerError)
387 // return
388 // }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400389 if name, err := s.nameGenerator.Generate(); err != nil {
390 http.Error(w, err.Error(), http.StatusInternalServerError)
391 return
392 } else {
393 req.Name = name
394 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400395 var cidrs installer.EnvCIDRs
gioe72b54f2024-04-22 10:44:41 +0400396 if err := soft.ReadYaml(s.repo, "env-cidrs.yaml", &cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400397 http.Error(w, err.Error(), http.StatusInternalServerError)
398 return
399 }
400 startIP, err := findNextStartIP(cidrs)
401 if err != nil {
402 http.Error(w, err.Error(), http.StatusInternalServerError)
403 return
404 }
405 cidrs = append(cidrs, installer.EnvCIDR{req.Name, startIP})
gioe72b54f2024-04-22 10:44:41 +0400406 if err := soft.WriteYaml(s.repo, "env-cidrs.yaml", cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400407 http.Error(w, err.Error(), http.StatusInternalServerError)
408 return
409 }
giob4a3a192024-08-19 09:55:47 +0400410 if _, err := s.repo.CommitAndPush(fmt.Sprintf("Allocate CIDR for %s", req.Name)); err != nil {
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400411 http.Error(w, err.Error(), http.StatusInternalServerError)
412 return
413 }
gioe72b54f2024-04-22 10:44:41 +0400414 envNetwork, err := installer.NewEnvNetwork(startIP)
415 if err != nil {
416 http.Error(w, err.Error(), http.StatusInternalServerError)
417 return
418 }
gio7841f4f2024-07-26 19:53:49 +0400419 privateDomain := ""
420 if req.PrivateNetworkSubdomain != "" {
421 privateDomain = fmt.Sprintf("%s.%s", req.PrivateNetworkSubdomain, req.Domain)
422 }
gioe72b54f2024-04-22 10:44:41 +0400423 env := installer.EnvConfig{
424 Id: req.Name,
425 InfraName: infra.Name,
426 Domain: req.Domain,
gio7841f4f2024-07-26 19:53:49 +0400427 PrivateDomain: privateDomain,
gioe72b54f2024-04-22 10:44:41 +0400428 ContactEmail: req.ContactEmail,
429 AdminPublicKey: req.AdminPublicKey,
430 PublicIP: infra.PublicIP,
431 NameserverIP: infra.PublicIP,
432 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
433 Network: envNetwork,
434 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400435 key := func() string {
436 for {
437 key, err := s.nameGenerator.Generate()
438 if err == nil {
439 return key
440 }
441 }
442 }()
443 infoUpdater := func(info string) {
444 s.envInfo[key] = template.HTML(markdown.ToHTML([]byte(info), nil, nil))
445 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400446 t, dns := tasks.NewCreateEnvTask(
gioe72b54f2024-04-22 10:44:41 +0400447 env,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400448 s.nsCreator,
giof8843412024-05-22 16:38:05 +0400449 s.jc,
450 s.hf,
gioe72b54f2024-04-22 10:44:41 +0400451 s.dnsFetcher,
452 s.httpClient,
453 s.dnsClient,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400454 s.repo,
gioe72b54f2024-04-22 10:44:41 +0400455 s.repoClient,
gio3cdee592024-04-17 10:15:56 +0400456 mgr,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400457 infoUpdater,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400458 )
giod9c398e2024-06-06 13:33:03 +0400459 if err := s.Tasks.Add(key, t); err != nil {
460 panic(err)
461 }
462
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400463 s.dns[key] = dns
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400464 go t.Start()
Giorgi Lekveishvilic85504d2023-12-20 19:29:47 +0400465 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400466}
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400467
468func findNextStartIP(cidrs installer.EnvCIDRs) (net.IP, error) {
469 m, err := netip.ParseAddr("10.0.0.0")
470 if err != nil {
471 return nil, err
472 }
473 for _, cidr := range cidrs {
474 i, err := netip.ParseAddr(cidr.IP.String())
475 if err != nil {
476 return nil, err
477 }
478 if i.Compare(m) > 0 {
479 m = i
480 }
481 }
482 sl := m.AsSlice()
483 sl[2]++
484 if sl[2] == 0b11111111 {
485 sl[2] = 0
486 sl[1]++
487 }
488 if sl[1] == 0b11111111 {
489 return nil, fmt.Errorf("Can not allocate")
490 }
491 ret, ok := netip.AddrFromSlice(sl)
492 if !ok {
493 return nil, fmt.Errorf("Must not reach")
494 }
495 return net.ParseIP(ret.String()), nil
496}