blob: 06ac89f225995915085c506939de62bc3f8cea00 [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"
15 "path"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040016 "path/filepath"
17 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040018 "text/template"
19
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040020 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040021 "github.com/charmbracelet/keygen"
22 "github.com/gorilla/mux"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040023 "github.com/miekg/dns"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040024
25 "github.com/giolekva/pcloud/core/installer"
26 "github.com/giolekva/pcloud/core/installer/soft"
27)
28
29//go:embed env-tmpl
30var filesTmpls embed.FS
31
32//go:embed create-env.html
33var createEnvFormHtml string
34
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040035//go:embed env-created.html
36var envCreatedHtml string
37
38type Status string
39
40const (
41 StatusActive Status = "ACTIVE"
42 StatusAccepted Status = "ACCEPTED"
43)
44
45// TODO(giolekva): add CreatedAt and ValidUntil
46type invitation struct {
47 Token string `json:"token"`
48 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040049}
50
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040051type EnvServer struct {
52 port int
53 ss *soft.Client
54 repo installer.RepoIO
55 nsCreator installer.NamespaceCreator
56 nameGenerator installer.NameGenerator
57}
58
59func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator, nameGenerator installer.NameGenerator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040060 return &EnvServer{
61 port,
62 ss,
63 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040064 nsCreator,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040065 nameGenerator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040066 }
67}
68
69func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040070 r := mux.NewRouter()
71 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
72 r.Path("/env").Methods("GET").HandlerFunc(s.createEnvForm)
73 r.Path("/env").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040074 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040075 http.Handle("/", r)
76 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040077}
78
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040079func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040080 if _, err := w.Write([]byte(createEnvFormHtml)); err != nil {
81 http.Error(w, err.Error(), http.StatusInternalServerError)
82 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040083}
84
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040085func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
86 invitations, err := s.readInvitations()
87 if err != nil {
88 http.Error(w, err.Error(), http.StatusInternalServerError)
89 return
90 }
91 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
92 if err != nil {
93 http.Error(w, err.Error(), http.StatusInternalServerError)
94 return
95
96 }
97 invitations = append(invitations, invitation{token, StatusActive})
98 if err := s.writeInvitations(invitations); err != nil {
99 http.Error(w, err.Error(), http.StatusInternalServerError)
100 return
101 }
102 if _, err := w.Write([]byte("OK")); err != nil {
103 http.Error(w, err.Error(), http.StatusInternalServerError)
104 return
105 }
106}
107
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400108type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400109 Name string
110 ContactEmail string `json:"contactEmail"`
111 Domain string `json:"domain"`
112 AdminPublicKey string `json:"adminPublicKey"`
113 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400114}
115
116func (s *EnvServer) readInvitations() ([]invitation, error) {
117 r, err := s.repo.Reader("invitations")
118 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400119 if errors.Is(err, fs.ErrNotExist) {
120 return make([]invitation, 0), nil
121 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400122 return nil, err
123 }
124 defer r.Close()
125 dec := json.NewDecoder(r)
126 invitations := make([]invitation, 0)
127 for {
128 var i invitation
129 if err := dec.Decode(&i); err == io.EOF {
130 break
131 }
132 invitations = append(invitations, i)
133 }
134 return invitations, nil
135}
136
137func (s *EnvServer) writeInvitations(invitations []invitation) error {
138 w, err := s.repo.Writer("invitations")
139 if err != nil {
140 return err
141 }
142 defer w.Close()
143 enc := json.NewEncoder(w)
144 for _, i := range invitations {
145 if err := enc.Encode(i); err != nil {
146 return err
147 }
148 }
149 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400150}
151
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400152func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400153 var req createEnvReq
154 if err := func() error {
155 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400156 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400157 return err
158 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400159 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400160 return err
161 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400162 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400163 return err
164 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400165 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400166 return err
167 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400168 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
169 return err
170 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400171 return nil
172 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400173 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400174 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400175 }
176 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400177 return req, nil
178}
179
180func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400181 invitations, err := s.readInvitations()
182 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400183 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400184 }
185 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400186 for i := range invitations {
187 if invitations[i].Token == token && invitations[i].Status == StatusActive {
188 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400189 found = true
190 break
191 }
192 }
193 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400194 return fmt.Errorf("Invitation not found")
195 }
196 return s.writeInvitations(invitations)
197}
198
199func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
200 req, err := extractRequest(r)
201 if err != nil {
202 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400203 return
204 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400205 var env installer.EnvConfig
206 cr, err := s.repo.Reader("config.yaml")
207 if err != nil {
208 http.Error(w, err.Error(), http.StatusInternalServerError)
209 return
210 }
211 defer cr.Close()
212 if err := installer.ReadYaml(cr, &env); err != nil {
213 http.Error(w, err.Error(), http.StatusInternalServerError)
214 return
215 }
216 if err := s.acceptInvitation(req.SecretToken); err != nil {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400217 http.Error(w, err.Error(), http.StatusInternalServerError)
218 return
219 }
220 if name, err := s.nameGenerator.Generate(); err != nil {
221 http.Error(w, err.Error(), http.StatusInternalServerError)
222 return
223 } else {
224 req.Name = name
225 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400226 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
227 ssApp, err := appsRepo.Find("soft-serve")
228 if err != nil {
229 http.Error(w, err.Error(), http.StatusInternalServerError)
230 return
231 }
232 ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", req.Name))
233 if err != nil {
234 http.Error(w, err.Error(), http.StatusInternalServerError)
235 return
236 }
237 ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", req.Name))
238 if err != nil {
239 http.Error(w, err.Error(), http.StatusInternalServerError)
240 return
241 }
242 ssValues := map[string]any{
243 "ChartRepositoryNamespace": env.Name,
244 "ServiceType": "ClusterIP",
245 "PrivateKey": string(ssKeys.RawPrivateKey()),
246 "PublicKey": string(ssKeys.RawAuthorizedKey()),
247 "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
248 "Ingress": map[string]any{
249 "Enabled": false,
250 },
251 }
252 derived := installer.Derived{
253 Global: installer.Values{
254 Id: req.Name,
255 PCloudEnvName: env.Name,
256 },
257 Release: installer.Release{
258 Namespace: req.Name,
259 },
260 Values: ssValues,
261 }
262 if err := s.nsCreator.Create(req.Name); err != nil {
263 http.Error(w, err.Error(), http.StatusInternalServerError)
264 return
265 }
266 if err := s.repo.InstallApp(*ssApp, filepath.Join("/environments", req.Name, "config-repo"), ssValues, derived); err != nil {
267 http.Error(w, err.Error(), http.StatusInternalServerError)
268 return
269 }
270 k := installer.NewKustomization()
271 k.AddResources("config-repo")
272 if err := s.repo.WriteKustomization(filepath.Join("/environments", req.Name, "kustomization.yaml"), k); err != nil {
273 http.Error(w, err.Error(), http.StatusInternalServerError)
274 return
275 }
276 ssClient, err := soft.WaitForClient(
277 fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", req.Name, 22),
278 ssAdminKeys.RawPrivateKey(),
279 log.Default())
280 if err != nil {
281 http.Error(w, err.Error(), http.StatusInternalServerError)
282 return
283 }
284 if err := ssClient.AddPublicKey("admin", req.AdminPublicKey); err != nil {
285 http.Error(w, err.Error(), http.StatusInternalServerError)
286 return
287 }
288 defer func() {
289 if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
290 http.Error(w, err.Error(), http.StatusInternalServerError)
291 return
292 }
293 }()
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400294 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
295 keys, err := installer.NewSSHKeyPair(fluxUserName)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400296 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400297 http.Error(w, err.Error(), http.StatusInternalServerError)
298 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400299 }
300 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400301 if err := ssClient.AddRepository("config"); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400302 http.Error(w, err.Error(), http.StatusInternalServerError)
303 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400304 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400305 repo, err := ssClient.GetRepo("config")
306 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400307 http.Error(w, err.Error(), http.StatusInternalServerError)
308 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400309 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400310 repoIO := installer.NewRepoIO(repo, ssClient.Signer)
311 if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", req.Name), "readme"); err != nil {
312 http.Error(w, err.Error(), http.StatusInternalServerError)
313 return
314 }
315 if err := ssClient.AddUser(fluxUserName, keys.AuthorizedKey()); err != nil {
316 http.Error(w, err.Error(), http.StatusInternalServerError)
317 return
318 }
319 if err := ssClient.AddReadOnlyCollaborator("config", fluxUserName); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400320 http.Error(w, err.Error(), http.StatusInternalServerError)
321 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400322 }
323 }
324 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400325 repo, err := ssClient.GetRepo("config")
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400326 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400327 http.Error(w, err.Error(), http.StatusInternalServerError)
328 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400329 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400330 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 +0400331 http.Error(w, err.Error(), http.StatusInternalServerError)
332 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400333 }
334 }
335 {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400336 ssPublicKeys, err := ssClient.GetPublicKeys()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400337 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400338 http.Error(w, err.Error(), http.StatusInternalServerError)
339 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400340 }
341 if err := addNewEnv(
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400342 s.repo,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400343 req,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400344 strings.Split(ssClient.Addr, ":")[0],
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400345 keys,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400346 ssPublicKeys,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400347 ); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400348 http.Error(w, err.Error(), http.StatusInternalServerError)
349 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400350 }
351 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400352 tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
353 if err != nil {
354 http.Error(w, err.Error(), http.StatusInternalServerError)
355 return
356 }
357 if err := tmpl.Execute(w, map[string]any{
358 "Domain": req.Domain,
359 }); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400360 http.Error(w, err.Error(), http.StatusInternalServerError)
361 return
362 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400363}
364
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400365type DNSSecKey struct {
366 Basename string `json:"basename,omitempty"`
367 Key []byte `json:"key,omitempty"`
368 Private []byte `json:"private,omitempty"`
369 DS []byte `json:"ds,omitempty"`
370}
371
372func newDNSSecKey(zone string) (DNSSecKey, error) {
373 key := &dns.DNSKEY{
374 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
375 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
376 }
377 priv, err := key.Generate(256)
378 if err != nil {
379 return DNSSecKey{}, err
380 }
381 return DNSSecKey{
382 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
383 Key: []byte(key.String()),
384 Private: []byte(key.PrivateKeyString(priv)),
385 DS: []byte(key.ToDS(dns.SHA256).String()),
386 }, nil
387}
388
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400389func initNewEnv(
390 ss *soft.Client,
391 r installer.RepoIO,
392 nsCreator installer.NamespaceCreator,
393 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400394 pcloudEnvName string,
395 pcloudPublicIP string,
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400396) error {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400397 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400398 if err != nil {
399 return err
400 }
401 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400402 // TODO(giolekva): private domain can be configurable as well
403 config := installer.Config{
404 Values: installer.Values{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400405 PCloudEnvName: pcloudEnvName,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400406 Id: req.Name,
407 ContactEmail: req.ContactEmail,
408 Domain: req.Domain,
409 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400410 PublicIP: pcloudPublicIP,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400411 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
412 },
413 }
414 if err := r.WriteYaml("config.yaml", config); err != nil {
415 return err
416 }
417 {
418 out, err := r.Writer("pcloud-charts.yaml")
419 if err != nil {
420 return err
421 }
422 defer out.Close()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400423 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400424apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400425kind: GitRepository
426metadata:
427 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400428 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400429spec:
430 interval: 1m0s
431 url: https://github.com/giolekva/pcloud
432 ref:
433 branch: main
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400434`, req.Name)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400435 if err != nil {
436 return err
437 }
438 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400439 {
440 key, err := newDNSSecKey(req.Domain)
441 if err != nil {
442 return err
443 }
444 out, err := r.Writer("dns-zone.yaml")
445 if err != nil {
446 return err
447 }
448 defer out.Close()
449 dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
450apiVersion: dodo.cloud.dodo.cloud/v1
451kind: DNSZone
452metadata:
453 name: dns-zone
454 namespace: {{ .namespace }}
455spec:
456 zone: {{ .zone }}
457 privateIP: 10.1.0.1
458 publicIPs:
459 - 135.181.48.180
460 - 65.108.39.172
461 - 65.108.39.171
462 nameservers:
463 - 135.181.48.180
464 - 65.108.39.172
465 - 65.108.39.171
466 dnssec:
467 enabled: true
468 secretName: dnssec-key
469---
470apiVersion: v1
471kind: Secret
472metadata:
473 name: dnssec-key
474 namespace: {{ .namespace }}
475type: Opaque
476data:
477 basename: {{ .dnssec.Basename | b64enc }}
478 key: {{ .dnssec.Key | toString | b64enc }}
479 private: {{ .dnssec.Private | toString | b64enc }}
480 ds: {{ .dnssec.DS | toString | b64enc }}
481`)
482 if err != nil {
483 return err
484 }
485 if err := dnsZoneTmpl.Execute(out, map[string]any{
486 "namespace": req.Name,
487 "zone": req.Domain,
488 "dnssec": key,
489 }); err != nil {
490 return err
491 }
492 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400493 rootKust := installer.NewKustomization()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400494 rootKust.AddResources("pcloud-charts.yaml", "dns-zone.yaml", "apps")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400495 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
496 return err
497 }
498 appsKust := installer.NewKustomization()
499 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
500 return err
501 }
502 r.CommitAndPush("initialize config")
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400503 nsGen := installer.NewPrefixGenerator(req.Name + "-")
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400504 emptySuffixGen := installer.NewEmptySuffixGenerator()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400505 {
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400506 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400507 if err != nil {
508 return err
509 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400510 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400511 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
512 "From": "10.1.0.1",
513 "To": "10.1.0.1",
514 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400515 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400516 }); err != nil {
517 return err
518 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400519 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400520 "Name": fmt.Sprintf("%s-headscale", req.Name),
521 "From": "10.1.0.2",
522 "To": "10.1.0.2",
523 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400524 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400525 }); err != nil {
526 return err
527 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400528 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400529 "Name": req.Name,
530 "From": "10.1.0.100",
531 "To": "10.1.0.254",
532 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400533 "Namespace": "metallb-system",
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400534 }); err != nil {
535 return err
536 }
537 }
538 {
539 app, err := appsRepo.Find("ingress-private")
540 if err != nil {
541 return err
542 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400543 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400544 return err
545 }
546 }
547 {
548 app, err := appsRepo.Find("certificate-issuer-public")
549 if err != nil {
550 return err
551 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400552 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400553 return err
554 }
555 }
556 {
557 app, err := appsRepo.Find("core-auth")
558 if err != nil {
559 return err
560 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400561 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400562 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
563 }); err != nil {
564 return err
565 }
566 }
567 {
568 app, err := appsRepo.Find("headscale")
569 if err != nil {
570 return err
571 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400572 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400573 "Subdomain": "headscale",
574 }); err != nil {
575 return err
576 }
577 }
578 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400579 keys, err := installer.NewSSHKeyPair("welcome")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400580 if err != nil {
581 return err
582 }
583 user := fmt.Sprintf("%s-welcome", req.Name)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400584 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400585 return err
586 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400587 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400588 return err
589 }
590 app, err := appsRepo.Find("welcome")
591 if err != nil {
592 return err
593 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400594 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400595 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400596 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400597 }); err != nil {
598 return err
599 }
600 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400601 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400602 user := fmt.Sprintf("%s-appmanager", req.Name)
603 keys, err := installer.NewSSHKeyPair(user)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400604 if err != nil {
605 return err
606 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400607 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400608 return err
609 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400610 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400611 return err
612 }
613 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
614 if err != nil {
615 return err
616 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400617 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400618 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400619 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400620 }); err != nil {
621 return err
622 }
623 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400624 return nil
625}
626
627func addNewEnv(
628 repoIO installer.RepoIO,
629 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400630 repoHost string,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400631 keys *keygen.KeyPair,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400632 configRepoPublicKeys []string,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400633) error {
634 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
635 if err != nil {
636 return err
637 }
638 kust.AddResources(req.Name)
639 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
640 if err != nil {
641 return err
642 }
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400643 var knownHosts bytes.Buffer
644 for _, key := range configRepoPublicKeys {
645 fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
646 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400647 for _, tmpl := range tmpls.Templates() {
648 dstPath := path.Join("environments", req.Name, tmpl.Name())
649 dst, err := repoIO.Writer(dstPath)
650 if err != nil {
651 return err
652 }
653 defer dst.Close()
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400654
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400655 if err := tmpl.Execute(dst, map[string]string{
656 "Name": req.Name,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400657 "PrivateKey": base64.StdEncoding.EncodeToString(keys.RawPrivateKey()),
658 "PublicKey": base64.StdEncoding.EncodeToString(keys.RawAuthorizedKey()),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400659 "RepoHost": repoHost,
660 "RepoName": "config",
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400661 "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400662 }); err != nil {
663 return err
664 }
665 }
666 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
667 return err
668 }
669 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
670 return err
671 }
672 return nil
673}