blob: c0f2f01f9654032360743766516d4cb771ab6be1 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +04004 "bytes"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04005 "embed"
6 "encoding/base64"
7 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04008 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04009 "fmt"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040010 htemplate "html/template"
11 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040012 "io/fs"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040013 "log"
14 "net/http"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040015 "net/netip"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040016 "path"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040017 "path/filepath"
18 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040019 "text/template"
20
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040021 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040022 "github.com/charmbracelet/keygen"
23 "github.com/gorilla/mux"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040024 "github.com/miekg/dns"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040025
26 "github.com/giolekva/pcloud/core/installer"
27 "github.com/giolekva/pcloud/core/installer/soft"
28)
29
30//go:embed env-tmpl
31var filesTmpls embed.FS
32
33//go:embed create-env.html
34var createEnvFormHtml string
35
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040036//go:embed env-created.html
37var envCreatedHtml string
38
39type Status string
40
41const (
42 StatusActive Status = "ACTIVE"
43 StatusAccepted Status = "ACCEPTED"
44)
45
46// TODO(giolekva): add CreatedAt and ValidUntil
47type invitation struct {
48 Token string `json:"token"`
49 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040050}
51
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040052type EnvServer struct {
53 port int
54 ss *soft.Client
55 repo installer.RepoIO
56 nsCreator installer.NamespaceCreator
57 nameGenerator installer.NameGenerator
58}
59
60func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator, nameGenerator installer.NameGenerator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040061 return &EnvServer{
62 port,
63 ss,
64 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040065 nsCreator,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040066 nameGenerator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040067 }
68}
69
70func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040071 r := mux.NewRouter()
72 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040073 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
74 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040075 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040076 http.Handle("/", r)
77 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040078}
79
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040080func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040081 if _, err := w.Write([]byte(createEnvFormHtml)); err != nil {
82 http.Error(w, err.Error(), http.StatusInternalServerError)
83 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040084}
85
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040086func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
87 invitations, err := s.readInvitations()
88 if err != nil {
89 http.Error(w, err.Error(), http.StatusInternalServerError)
90 return
91 }
92 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
93 if err != nil {
94 http.Error(w, err.Error(), http.StatusInternalServerError)
95 return
96
97 }
98 invitations = append(invitations, invitation{token, StatusActive})
99 if err := s.writeInvitations(invitations); err != nil {
100 http.Error(w, err.Error(), http.StatusInternalServerError)
101 return
102 }
103 if _, err := w.Write([]byte("OK")); err != nil {
104 http.Error(w, err.Error(), http.StatusInternalServerError)
105 return
106 }
107}
108
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400109type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400110 Name string
111 ContactEmail string `json:"contactEmail"`
112 Domain string `json:"domain"`
113 AdminPublicKey string `json:"adminPublicKey"`
114 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400115}
116
117func (s *EnvServer) readInvitations() ([]invitation, error) {
118 r, err := s.repo.Reader("invitations")
119 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400120 if errors.Is(err, fs.ErrNotExist) {
121 return make([]invitation, 0), nil
122 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400123 return nil, err
124 }
125 defer r.Close()
126 dec := json.NewDecoder(r)
127 invitations := make([]invitation, 0)
128 for {
129 var i invitation
130 if err := dec.Decode(&i); err == io.EOF {
131 break
132 }
133 invitations = append(invitations, i)
134 }
135 return invitations, nil
136}
137
138func (s *EnvServer) writeInvitations(invitations []invitation) error {
139 w, err := s.repo.Writer("invitations")
140 if err != nil {
141 return err
142 }
143 defer w.Close()
144 enc := json.NewEncoder(w)
145 for _, i := range invitations {
146 if err := enc.Encode(i); err != nil {
147 return err
148 }
149 }
150 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400151}
152
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400153func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400154 var req createEnvReq
155 if err := func() error {
156 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400157 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400158 return err
159 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400160 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400161 return err
162 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400163 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400164 return err
165 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400166 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400167 return err
168 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400169 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
170 return err
171 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400172 return nil
173 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400174 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400175 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400176 }
177 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400178 return req, nil
179}
180
181func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400182 invitations, err := s.readInvitations()
183 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400184 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400185 }
186 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400187 for i := range invitations {
188 if invitations[i].Token == token && invitations[i].Status == StatusActive {
189 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400190 found = true
191 break
192 }
193 }
194 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400195 return fmt.Errorf("Invitation not found")
196 }
197 return s.writeInvitations(invitations)
198}
199
200func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
201 req, err := extractRequest(r)
202 if err != nil {
203 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400204 return
205 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400206 var env installer.EnvConfig
207 cr, err := s.repo.Reader("config.yaml")
208 if err != nil {
209 http.Error(w, err.Error(), http.StatusInternalServerError)
210 return
211 }
212 defer cr.Close()
213 if err := installer.ReadYaml(cr, &env); err != nil {
214 http.Error(w, err.Error(), http.StatusInternalServerError)
215 return
216 }
217 if err := s.acceptInvitation(req.SecretToken); err != nil {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400218 http.Error(w, err.Error(), http.StatusInternalServerError)
219 return
220 }
221 if name, err := s.nameGenerator.Generate(); err != nil {
222 http.Error(w, err.Error(), http.StatusInternalServerError)
223 return
224 } else {
225 req.Name = name
226 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400227 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
228 ssApp, err := appsRepo.Find("soft-serve")
229 if err != nil {
230 http.Error(w, err.Error(), http.StatusInternalServerError)
231 return
232 }
233 ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", req.Name))
234 if err != nil {
235 http.Error(w, err.Error(), http.StatusInternalServerError)
236 return
237 }
238 ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", req.Name))
239 if err != nil {
240 http.Error(w, err.Error(), http.StatusInternalServerError)
241 return
242 }
243 ssValues := map[string]any{
244 "ChartRepositoryNamespace": env.Name,
245 "ServiceType": "ClusterIP",
246 "PrivateKey": string(ssKeys.RawPrivateKey()),
247 "PublicKey": string(ssKeys.RawAuthorizedKey()),
248 "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
249 "Ingress": map[string]any{
250 "Enabled": false,
251 },
252 }
253 derived := installer.Derived{
254 Global: installer.Values{
255 Id: req.Name,
256 PCloudEnvName: env.Name,
257 },
258 Release: installer.Release{
259 Namespace: req.Name,
260 },
261 Values: ssValues,
262 }
263 if err := s.nsCreator.Create(req.Name); err != nil {
264 http.Error(w, err.Error(), http.StatusInternalServerError)
265 return
266 }
267 if err := s.repo.InstallApp(*ssApp, filepath.Join("/environments", req.Name, "config-repo"), ssValues, derived); err != nil {
268 http.Error(w, err.Error(), http.StatusInternalServerError)
269 return
270 }
271 k := installer.NewKustomization()
272 k.AddResources("config-repo")
273 if err := s.repo.WriteKustomization(filepath.Join("/environments", req.Name, "kustomization.yaml"), k); err != nil {
274 http.Error(w, err.Error(), http.StatusInternalServerError)
275 return
276 }
277 ssClient, err := soft.WaitForClient(
278 fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", req.Name, 22),
279 ssAdminKeys.RawPrivateKey(),
280 log.Default())
281 if err != nil {
282 http.Error(w, err.Error(), http.StatusInternalServerError)
283 return
284 }
285 if err := ssClient.AddPublicKey("admin", req.AdminPublicKey); err != nil {
286 http.Error(w, err.Error(), http.StatusInternalServerError)
287 return
288 }
289 defer func() {
290 if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
291 http.Error(w, err.Error(), http.StatusInternalServerError)
292 return
293 }
294 }()
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400295 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
296 keys, err := installer.NewSSHKeyPair(fluxUserName)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400297 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400298 http.Error(w, err.Error(), http.StatusInternalServerError)
299 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400300 }
301 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400302 if err := ssClient.AddRepository("config"); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400303 http.Error(w, err.Error(), http.StatusInternalServerError)
304 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400305 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400306 repo, err := ssClient.GetRepo("config")
307 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400308 http.Error(w, err.Error(), http.StatusInternalServerError)
309 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400310 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400311 repoIO := installer.NewRepoIO(repo, ssClient.Signer)
312 if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", req.Name), "readme"); err != nil {
313 http.Error(w, err.Error(), http.StatusInternalServerError)
314 return
315 }
316 if err := ssClient.AddUser(fluxUserName, keys.AuthorizedKey()); err != nil {
317 http.Error(w, err.Error(), http.StatusInternalServerError)
318 return
319 }
320 if err := ssClient.AddReadOnlyCollaborator("config", fluxUserName); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400321 http.Error(w, err.Error(), http.StatusInternalServerError)
322 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400323 }
324 }
325 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400326 repo, err := ssClient.GetRepo("config")
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400327 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400328 http.Error(w, err.Error(), http.StatusInternalServerError)
329 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400330 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400331 if err := initNewEnv(ssClient, installer.NewRepoIO(repo, ssClient.Signer), s.nsCreator, req, env.Name, env.PublicIP); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400332 http.Error(w, err.Error(), http.StatusInternalServerError)
333 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400334 }
335 }
336 {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400337 ssPublicKeys, err := ssClient.GetPublicKeys()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400338 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400339 http.Error(w, err.Error(), http.StatusInternalServerError)
340 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400341 }
342 if err := addNewEnv(
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400343 s.repo,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400344 req,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400345 strings.Split(ssClient.Addr, ":")[0],
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400346 keys,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400347 ssPublicKeys,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400348 ); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400349 http.Error(w, err.Error(), http.StatusInternalServerError)
350 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400351 }
352 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400353 tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
354 if err != nil {
355 http.Error(w, err.Error(), http.StatusInternalServerError)
356 return
357 }
358 if err := tmpl.Execute(w, map[string]any{
359 "Domain": req.Domain,
360 }); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400361 http.Error(w, err.Error(), http.StatusInternalServerError)
362 return
363 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400364}
365
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400366type DNSSecKey struct {
367 Basename string `json:"basename,omitempty"`
368 Key []byte `json:"key,omitempty"`
369 Private []byte `json:"private,omitempty"`
370 DS []byte `json:"ds,omitempty"`
371}
372
373func newDNSSecKey(zone string) (DNSSecKey, error) {
374 key := &dns.DNSKEY{
375 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
376 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
377 }
378 priv, err := key.Generate(256)
379 if err != nil {
380 return DNSSecKey{}, err
381 }
382 return DNSSecKey{
383 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
384 Key: []byte(key.String()),
385 Private: []byte(key.PrivateKeyString(priv)),
386 DS: []byte(key.ToDS(dns.SHA256).String()),
387 }, nil
388}
389
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400390func initNewEnv(
391 ss *soft.Client,
392 r installer.RepoIO,
393 nsCreator installer.NamespaceCreator,
394 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400395 pcloudEnvName string,
396 pcloudPublicIP string,
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400397) error {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400398 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400399 if err != nil {
400 return err
401 }
402 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400403 // TODO(giolekva): private domain can be configurable as well
404 config := installer.Config{
405 Values: installer.Values{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400406 PCloudEnvName: pcloudEnvName,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400407 Id: req.Name,
408 ContactEmail: req.ContactEmail,
409 Domain: req.Domain,
410 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400411 PublicIP: pcloudPublicIP,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400412 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
413 },
414 }
415 if err := r.WriteYaml("config.yaml", config); err != nil {
416 return err
417 }
418 {
419 out, err := r.Writer("pcloud-charts.yaml")
420 if err != nil {
421 return err
422 }
423 defer out.Close()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400424 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400425apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400426kind: GitRepository
427metadata:
428 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400429 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400430spec:
431 interval: 1m0s
432 url: https://github.com/giolekva/pcloud
433 ref:
434 branch: main
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400435`, req.Name)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400436 if err != nil {
437 return err
438 }
439 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400440 {
441 key, err := newDNSSecKey(req.Domain)
442 if err != nil {
443 return err
444 }
445 out, err := r.Writer("dns-zone.yaml")
446 if err != nil {
447 return err
448 }
449 defer out.Close()
450 dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
451apiVersion: dodo.cloud.dodo.cloud/v1
452kind: DNSZone
453metadata:
454 name: dns-zone
455 namespace: {{ .namespace }}
456spec:
457 zone: {{ .zone }}
458 privateIP: 10.1.0.1
459 publicIPs:
460 - 135.181.48.180
461 - 65.108.39.172
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400462 nameservers:
463 - 135.181.48.180
464 - 65.108.39.172
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400465 dnssec:
466 enabled: true
467 secretName: dnssec-key
468---
469apiVersion: v1
470kind: Secret
471metadata:
472 name: dnssec-key
473 namespace: {{ .namespace }}
474type: Opaque
475data:
476 basename: {{ .dnssec.Basename | b64enc }}
477 key: {{ .dnssec.Key | toString | b64enc }}
478 private: {{ .dnssec.Private | toString | b64enc }}
479 ds: {{ .dnssec.DS | toString | b64enc }}
480`)
481 if err != nil {
482 return err
483 }
484 if err := dnsZoneTmpl.Execute(out, map[string]any{
485 "namespace": req.Name,
486 "zone": req.Domain,
487 "dnssec": key,
488 }); err != nil {
489 return err
490 }
491 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400492 rootKust := installer.NewKustomization()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400493 rootKust.AddResources("pcloud-charts.yaml", "dns-zone.yaml", "apps")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400494 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
495 return err
496 }
497 appsKust := installer.NewKustomization()
498 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
499 return err
500 }
501 r.CommitAndPush("initialize config")
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400502 nsGen := installer.NewPrefixGenerator(req.Name + "-")
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400503 emptySuffixGen := installer.NewEmptySuffixGenerator()
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400504 ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
505 if err != nil {
506 return err
507 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400508 {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400509 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400510 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400511 if err != nil {
512 return err
513 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400514 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400515 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400516 "From": ingressPrivateIP.String(),
517 "To": ingressPrivateIP.String(),
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400518 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400519 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400520 }); err != nil {
521 return err
522 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400523 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400524 "Name": fmt.Sprintf("%s-headscale", req.Name),
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400525 "From": headscaleIP.String(),
526 "To": headscaleIP.String(),
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400527 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400528 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400529 }); err != nil {
530 return err
531 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400532 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400533 "Name": req.Name,
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400534 "From": "10.1.0.100", // TODO(gio): auto-generate
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400535 "To": "10.1.0.254",
536 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400537 "Namespace": "metallb-system",
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400538 }); err != nil {
539 return err
540 }
541 }
542 {
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400543 app, err := appsRepo.Find("private-network")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400544 if err != nil {
545 return err
546 }
547 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400548 "PrivateNetwork": map[string]any{
549 "Hostname": "private-network-proxy",
550 "Username": "private-network-proxy",
551 "IPSubnet": "10.1.0.0/24",
552 },
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400553 }); err != nil {
554 return err
555 }
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400556 }
557 {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400558 app, err := appsRepo.Find("certificate-issuer-public")
559 if err != nil {
560 return err
561 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400562 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400563 return err
564 }
565 }
566 {
Giorgi Lekveishvilicced4c32023-12-08 08:56:40 +0400567 app, err := appsRepo.Find("certificate-issuer-private")
568 if err != nil {
569 return err
570 }
571 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
572 "APIConfigMap": map[string]any{
573 "Name": "api-config", // TODO(gio): take from global pcloud config
574 "Namespace": fmt.Sprintf("%s-dns-zone-manager", pcloudEnvName),
575 },
576 }); err != nil {
577 return err
578 }
579 }
580 {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400581 app, err := appsRepo.Find("core-auth")
582 if err != nil {
583 return err
584 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400585 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400586 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
587 }); err != nil {
588 return err
589 }
590 }
591 {
592 app, err := appsRepo.Find("headscale")
593 if err != nil {
594 return err
595 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400596 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400597 "Subdomain": "headscale",
598 }); err != nil {
599 return err
600 }
601 }
602 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400603 keys, err := installer.NewSSHKeyPair("welcome")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400604 if err != nil {
605 return err
606 }
607 user := fmt.Sprintf("%s-welcome", req.Name)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400608 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400609 return err
610 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400611 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400612 return err
613 }
614 app, err := appsRepo.Find("welcome")
615 if err != nil {
616 return err
617 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400618 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400619 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400620 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400621 }); err != nil {
622 return err
623 }
624 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400625 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400626 user := fmt.Sprintf("%s-appmanager", req.Name)
627 keys, err := installer.NewSSHKeyPair(user)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400628 if err != nil {
629 return err
630 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400631 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400632 return err
633 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400634 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400635 return err
636 }
637 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
638 if err != nil {
639 return err
640 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400641 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400642 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400643 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400644 }); err != nil {
645 return err
646 }
647 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400648 return nil
649}
650
651func addNewEnv(
652 repoIO installer.RepoIO,
653 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400654 repoHost string,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400655 keys *keygen.KeyPair,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400656 configRepoPublicKeys []string,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400657) error {
658 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
659 if err != nil {
660 return err
661 }
662 kust.AddResources(req.Name)
663 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
664 if err != nil {
665 return err
666 }
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400667 var knownHosts bytes.Buffer
668 for _, key := range configRepoPublicKeys {
669 fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
670 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400671 for _, tmpl := range tmpls.Templates() {
672 dstPath := path.Join("environments", req.Name, tmpl.Name())
673 dst, err := repoIO.Writer(dstPath)
674 if err != nil {
675 return err
676 }
677 defer dst.Close()
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400678
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400679 if err := tmpl.Execute(dst, map[string]string{
680 "Name": req.Name,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400681 "PrivateKey": base64.StdEncoding.EncodeToString(keys.RawPrivateKey()),
682 "PublicKey": base64.StdEncoding.EncodeToString(keys.RawAuthorizedKey()),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400683 "RepoHost": repoHost,
684 "RepoName": "config",
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400685 "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400686 }); err != nil {
687 return err
688 }
689 }
690 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
691 return err
692 }
693 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
694 return err
695 }
696 return nil
697}