blob: 2c5d332dcaacd84f17c17c907f72e1c7e20bdc23 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
4 "embed"
5 "encoding/base64"
6 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04007 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04008 "fmt"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +04009 htemplate "html/template"
10 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040011 "io/fs"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040012 "log"
13 "net/http"
14 "path"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040015 "path/filepath"
16 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040017 "text/template"
18
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040019 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040020 "github.com/charmbracelet/keygen"
21 "github.com/gorilla/mux"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040022 "github.com/miekg/dns"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040023
24 "github.com/giolekva/pcloud/core/installer"
25 "github.com/giolekva/pcloud/core/installer/soft"
26)
27
28//go:embed env-tmpl
29var filesTmpls embed.FS
30
31//go:embed create-env.html
32var createEnvFormHtml string
33
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040034//go:embed env-created.html
35var envCreatedHtml string
36
37type Status string
38
39const (
40 StatusActive Status = "ACTIVE"
41 StatusAccepted Status = "ACCEPTED"
42)
43
44// TODO(giolekva): add CreatedAt and ValidUntil
45type invitation struct {
46 Token string `json:"token"`
47 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040048}
49
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040050type EnvServer struct {
51 port int
52 ss *soft.Client
53 repo installer.RepoIO
54 nsCreator installer.NamespaceCreator
55 nameGenerator installer.NameGenerator
56}
57
58func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator, nameGenerator installer.NameGenerator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040059 return &EnvServer{
60 port,
61 ss,
62 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040063 nsCreator,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040064 nameGenerator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040065 }
66}
67
68func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040069 r := mux.NewRouter()
70 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
71 r.Path("/env").Methods("GET").HandlerFunc(s.createEnvForm)
72 r.Path("/env").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040073 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040074 http.Handle("/", r)
75 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040076}
77
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040078func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040079 if _, err := w.Write([]byte(createEnvFormHtml)); err != nil {
80 http.Error(w, err.Error(), http.StatusInternalServerError)
81 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040082}
83
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040084func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
85 invitations, err := s.readInvitations()
86 if err != nil {
87 http.Error(w, err.Error(), http.StatusInternalServerError)
88 return
89 }
90 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
91 if err != nil {
92 http.Error(w, err.Error(), http.StatusInternalServerError)
93 return
94
95 }
96 invitations = append(invitations, invitation{token, StatusActive})
97 if err := s.writeInvitations(invitations); err != nil {
98 http.Error(w, err.Error(), http.StatusInternalServerError)
99 return
100 }
101 if _, err := w.Write([]byte("OK")); err != nil {
102 http.Error(w, err.Error(), http.StatusInternalServerError)
103 return
104 }
105}
106
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400107type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400108 Name string
109 ContactEmail string `json:"contactEmail"`
110 Domain string `json:"domain"`
111 AdminPublicKey string `json:"adminPublicKey"`
112 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400113}
114
115func (s *EnvServer) readInvitations() ([]invitation, error) {
116 r, err := s.repo.Reader("invitations")
117 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400118 if errors.Is(err, fs.ErrNotExist) {
119 return make([]invitation, 0), nil
120 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400121 return nil, err
122 }
123 defer r.Close()
124 dec := json.NewDecoder(r)
125 invitations := make([]invitation, 0)
126 for {
127 var i invitation
128 if err := dec.Decode(&i); err == io.EOF {
129 break
130 }
131 invitations = append(invitations, i)
132 }
133 return invitations, nil
134}
135
136func (s *EnvServer) writeInvitations(invitations []invitation) error {
137 w, err := s.repo.Writer("invitations")
138 if err != nil {
139 return err
140 }
141 defer w.Close()
142 enc := json.NewEncoder(w)
143 for _, i := range invitations {
144 if err := enc.Encode(i); err != nil {
145 return err
146 }
147 }
148 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400149}
150
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400151func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400152 var req createEnvReq
153 if err := func() error {
154 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400155 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400156 return err
157 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400158 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400159 return err
160 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400161 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400162 return err
163 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400164 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400165 return err
166 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400167 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
168 return err
169 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400170 return nil
171 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400172 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400173 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400174 }
175 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400176 return req, nil
177}
178
179func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400180 invitations, err := s.readInvitations()
181 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400182 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400183 }
184 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400185 for i := range invitations {
186 if invitations[i].Token == token && invitations[i].Status == StatusActive {
187 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400188 found = true
189 break
190 }
191 }
192 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400193 return fmt.Errorf("Invitation not found")
194 }
195 return s.writeInvitations(invitations)
196}
197
198func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
199 req, err := extractRequest(r)
200 if err != nil {
201 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400202 return
203 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400204 var env installer.EnvConfig
205 cr, err := s.repo.Reader("config.yaml")
206 if err != nil {
207 http.Error(w, err.Error(), http.StatusInternalServerError)
208 return
209 }
210 defer cr.Close()
211 if err := installer.ReadYaml(cr, &env); err != nil {
212 http.Error(w, err.Error(), http.StatusInternalServerError)
213 return
214 }
215 if err := s.acceptInvitation(req.SecretToken); err != nil {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400216 http.Error(w, err.Error(), http.StatusInternalServerError)
217 return
218 }
219 if name, err := s.nameGenerator.Generate(); err != nil {
220 http.Error(w, err.Error(), http.StatusInternalServerError)
221 return
222 } else {
223 req.Name = name
224 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400225 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
226 ssApp, err := appsRepo.Find("soft-serve")
227 if err != nil {
228 http.Error(w, err.Error(), http.StatusInternalServerError)
229 return
230 }
231 ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", req.Name))
232 if err != nil {
233 http.Error(w, err.Error(), http.StatusInternalServerError)
234 return
235 }
236 ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", req.Name))
237 if err != nil {
238 http.Error(w, err.Error(), http.StatusInternalServerError)
239 return
240 }
241 ssValues := map[string]any{
242 "ChartRepositoryNamespace": env.Name,
243 "ServiceType": "ClusterIP",
244 "PrivateKey": string(ssKeys.RawPrivateKey()),
245 "PublicKey": string(ssKeys.RawAuthorizedKey()),
246 "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
247 "Ingress": map[string]any{
248 "Enabled": false,
249 },
250 }
251 derived := installer.Derived{
252 Global: installer.Values{
253 Id: req.Name,
254 PCloudEnvName: env.Name,
255 },
256 Release: installer.Release{
257 Namespace: req.Name,
258 },
259 Values: ssValues,
260 }
261 if err := s.nsCreator.Create(req.Name); err != nil {
262 http.Error(w, err.Error(), http.StatusInternalServerError)
263 return
264 }
265 if err := s.repo.InstallApp(*ssApp, filepath.Join("/environments", req.Name, "config-repo"), ssValues, derived); err != nil {
266 http.Error(w, err.Error(), http.StatusInternalServerError)
267 return
268 }
269 k := installer.NewKustomization()
270 k.AddResources("config-repo")
271 if err := s.repo.WriteKustomization(filepath.Join("/environments", req.Name, "kustomization.yaml"), k); err != nil {
272 http.Error(w, err.Error(), http.StatusInternalServerError)
273 return
274 }
275 ssClient, err := soft.WaitForClient(
276 fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", req.Name, 22),
277 ssAdminKeys.RawPrivateKey(),
278 log.Default())
279 if err != nil {
280 http.Error(w, err.Error(), http.StatusInternalServerError)
281 return
282 }
283 if err := ssClient.AddPublicKey("admin", req.AdminPublicKey); err != nil {
284 http.Error(w, err.Error(), http.StatusInternalServerError)
285 return
286 }
287 defer func() {
288 if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
289 http.Error(w, err.Error(), http.StatusInternalServerError)
290 return
291 }
292 }()
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400293 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
294 keys, err := installer.NewSSHKeyPair(fluxUserName)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400295 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400296 http.Error(w, err.Error(), http.StatusInternalServerError)
297 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400298 }
299 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400300 if err := ssClient.AddRepository("config"); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400301 http.Error(w, err.Error(), http.StatusInternalServerError)
302 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400303 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400304 repo, err := ssClient.GetRepo("config")
305 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400306 http.Error(w, err.Error(), http.StatusInternalServerError)
307 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400308 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400309 repoIO := installer.NewRepoIO(repo, ssClient.Signer)
310 if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", req.Name), "readme"); err != nil {
311 http.Error(w, err.Error(), http.StatusInternalServerError)
312 return
313 }
314 if err := ssClient.AddUser(fluxUserName, keys.AuthorizedKey()); err != nil {
315 http.Error(w, err.Error(), http.StatusInternalServerError)
316 return
317 }
318 if err := ssClient.AddReadOnlyCollaborator("config", fluxUserName); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400319 http.Error(w, err.Error(), http.StatusInternalServerError)
320 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400321 }
322 }
323 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400324 repo, err := ssClient.GetRepo("config")
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400325 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400326 http.Error(w, err.Error(), http.StatusInternalServerError)
327 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400328 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400329 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 +0400330 http.Error(w, err.Error(), http.StatusInternalServerError)
331 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400332 }
333 }
334 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400335 ssPubKey, err := ssClient.GetPublicKey()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400336 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400337 http.Error(w, err.Error(), http.StatusInternalServerError)
338 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400339 }
340 if err := addNewEnv(
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400341 s.repo,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400342 req,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400343 strings.Split(ssClient.Addr, ":")[0],
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400344 keys,
345 ssPubKey,
346 ); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400347 http.Error(w, err.Error(), http.StatusInternalServerError)
348 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400349 }
350 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400351 tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
352 if err != nil {
353 http.Error(w, err.Error(), http.StatusInternalServerError)
354 return
355 }
356 if err := tmpl.Execute(w, map[string]any{
357 "Domain": req.Domain,
358 }); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400359 http.Error(w, err.Error(), http.StatusInternalServerError)
360 return
361 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400362}
363
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400364type DNSSecKey struct {
365 Basename string `json:"basename,omitempty"`
366 Key []byte `json:"key,omitempty"`
367 Private []byte `json:"private,omitempty"`
368 DS []byte `json:"ds,omitempty"`
369}
370
371func newDNSSecKey(zone string) (DNSSecKey, error) {
372 key := &dns.DNSKEY{
373 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
374 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
375 }
376 priv, err := key.Generate(256)
377 if err != nil {
378 return DNSSecKey{}, err
379 }
380 return DNSSecKey{
381 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
382 Key: []byte(key.String()),
383 Private: []byte(key.PrivateKeyString(priv)),
384 DS: []byte(key.ToDS(dns.SHA256).String()),
385 }, nil
386}
387
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400388func initNewEnv(
389 ss *soft.Client,
390 r installer.RepoIO,
391 nsCreator installer.NamespaceCreator,
392 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400393 pcloudEnvName string,
394 pcloudPublicIP string,
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400395) error {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400396 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400397 if err != nil {
398 return err
399 }
400 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400401 // TODO(giolekva): private domain can be configurable as well
402 config := installer.Config{
403 Values: installer.Values{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400404 PCloudEnvName: pcloudEnvName,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400405 Id: req.Name,
406 ContactEmail: req.ContactEmail,
407 Domain: req.Domain,
408 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400409 PublicIP: pcloudPublicIP,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400410 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
411 },
412 }
413 if err := r.WriteYaml("config.yaml", config); err != nil {
414 return err
415 }
416 {
417 out, err := r.Writer("pcloud-charts.yaml")
418 if err != nil {
419 return err
420 }
421 defer out.Close()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400422 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400423apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400424kind: GitRepository
425metadata:
426 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400427 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400428spec:
429 interval: 1m0s
430 url: https://github.com/giolekva/pcloud
431 ref:
432 branch: main
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400433`, req.Name)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400434 if err != nil {
435 return err
436 }
437 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400438 {
439 key, err := newDNSSecKey(req.Domain)
440 if err != nil {
441 return err
442 }
443 out, err := r.Writer("dns-zone.yaml")
444 if err != nil {
445 return err
446 }
447 defer out.Close()
448 dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
449apiVersion: dodo.cloud.dodo.cloud/v1
450kind: DNSZone
451metadata:
452 name: dns-zone
453 namespace: {{ .namespace }}
454spec:
455 zone: {{ .zone }}
456 privateIP: 10.1.0.1
457 publicIPs:
458 - 135.181.48.180
459 - 65.108.39.172
460 - 65.108.39.171
461 nameservers:
462 - 135.181.48.180
463 - 65.108.39.172
464 - 65.108.39.171
465 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 Lekveishvilib4a9c982023-06-22 15:17:02 +0400504 {
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400505 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400506 if err != nil {
507 return err
508 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400509 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400510 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
511 "From": "10.1.0.1",
512 "To": "10.1.0.1",
513 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400514 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400515 }); err != nil {
516 return err
517 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400518 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400519 "Name": fmt.Sprintf("%s-headscale", req.Name),
520 "From": "10.1.0.2",
521 "To": "10.1.0.2",
522 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400523 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400524 }); err != nil {
525 return err
526 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400527 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400528 "Name": req.Name,
529 "From": "10.1.0.100",
530 "To": "10.1.0.254",
531 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400532 "Namespace": "metallb-system",
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400533 }); err != nil {
534 return err
535 }
536 }
537 {
538 app, err := appsRepo.Find("ingress-private")
539 if err != nil {
540 return err
541 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400542 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400543 return err
544 }
545 }
546 {
547 app, err := appsRepo.Find("certificate-issuer-public")
548 if err != nil {
549 return err
550 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400551 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400552 return err
553 }
554 }
555 {
556 app, err := appsRepo.Find("core-auth")
557 if err != nil {
558 return err
559 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400560 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400561 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
562 }); err != nil {
563 return err
564 }
565 }
566 {
567 app, err := appsRepo.Find("headscale")
568 if err != nil {
569 return err
570 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400571 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400572 "Subdomain": "headscale",
573 }); err != nil {
574 return err
575 }
576 }
577 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400578 keys, err := installer.NewSSHKeyPair("welcome")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400579 if err != nil {
580 return err
581 }
582 user := fmt.Sprintf("%s-welcome", req.Name)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400583 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400584 return err
585 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400586 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400587 return err
588 }
589 app, err := appsRepo.Find("welcome")
590 if err != nil {
591 return err
592 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400593 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400594 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400595 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400596 }); err != nil {
597 return err
598 }
599 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400600 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400601 user := fmt.Sprintf("%s-appmanager", req.Name)
602 keys, err := installer.NewSSHKeyPair(user)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400603 if err != nil {
604 return err
605 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400606 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400607 return err
608 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400609 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400610 return err
611 }
612 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
613 if err != nil {
614 return err
615 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400616 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400617 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400618 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400619 }); err != nil {
620 return err
621 }
622 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400623 return nil
624}
625
626func addNewEnv(
627 repoIO installer.RepoIO,
628 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400629 repoHost string,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400630 keys *keygen.KeyPair,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400631 pcloudRepoPublicKey []byte,
632) error {
633 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
634 if err != nil {
635 return err
636 }
637 kust.AddResources(req.Name)
638 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
639 if err != nil {
640 return err
641 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400642 for _, tmpl := range tmpls.Templates() {
643 dstPath := path.Join("environments", req.Name, tmpl.Name())
644 dst, err := repoIO.Writer(dstPath)
645 if err != nil {
646 return err
647 }
648 defer dst.Close()
649 if err := tmpl.Execute(dst, map[string]string{
650 "Name": req.Name,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400651 "PrivateKey": base64.StdEncoding.EncodeToString(keys.RawPrivateKey()),
652 "PublicKey": base64.StdEncoding.EncodeToString(keys.RawAuthorizedKey()),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400653 "RepoHost": repoHost,
654 "RepoName": "config",
655 "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", repoHost, pcloudRepoPublicKey))),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400656 }); err != nil {
657 return err
658 }
659 }
660 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
661 return err
662 }
663 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
664 return err
665 }
666 return nil
667}