blob: e08c073c00748a6a040c7a01c4b117d3878e4558 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
4 "embed"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "log"
9 "net/http"
10 "path"
11 "text/template"
12
13 "github.com/labstack/echo/v4"
14
15 "github.com/giolekva/pcloud/core/installer"
16 "github.com/giolekva/pcloud/core/installer/soft"
17)
18
19//go:embed env-tmpl
20var filesTmpls embed.FS
21
22//go:embed create-env.html
23var createEnvFormHtml string
24
25type EnvServer struct {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040026 port int
27 ss *soft.Client
28 repo installer.RepoIO
29 nsCreator installer.NamespaceCreator
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040030}
31
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040032func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040033 return &EnvServer{
34 port,
35 ss,
36 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040037 nsCreator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040038 }
39}
40
41func (s *EnvServer) Start() {
42 e := echo.New()
43 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
44 e.GET("/env", s.createEnvForm)
45 e.POST("/env", s.createEnv)
46 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
47}
48
49func (s *EnvServer) createEnvForm(c echo.Context) error {
50 return c.HTML(http.StatusOK, createEnvFormHtml)
51}
52
53type createEnvReq struct {
54 Name string `json:"name"`
55 ContactEmail string `json:"contactEmail"`
56 Domain string `json:"domain"`
57}
58
59func (s *EnvServer) createEnv(c echo.Context) error {
60 var req createEnvReq
61 if err := func() error {
62 var err error
63 f, err := c.FormParams()
64 if err != nil {
65 return err
66 }
67 if req.Name, err = getFormValue(f, "name"); err != nil {
68 return err
69 }
70 if req.Domain, err = getFormValue(f, "domain"); err != nil {
71 return err
72 }
73 if req.ContactEmail, err = getFormValue(f, "contact-email"); err != nil {
74 return err
75 }
76 return nil
77 }(); err != nil {
78 if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
79 return err
80 }
81 }
82 keys, err := installer.NewSSHKeyPair()
83 if err != nil {
84 return err
85 }
86 {
87 readme := fmt.Sprintf("# %s PCloud environment", req.Name)
88 if err := s.ss.AddRepository(req.Name, readme); err != nil {
89 return err
90 }
91 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
92 if err := s.ss.AddUser(fluxUserName, keys.Public); err != nil {
93 return err
94 }
95 if err := s.ss.AddCollaborator(req.Name, fluxUserName); err != nil {
96 return err
97 }
98 }
99 {
100 repo, err := s.ss.GetRepo(req.Name)
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400101 if err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400102 return err
103 }
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400104 var env installer.EnvConfig
105 r, err := s.repo.Reader("config.yaml")
106 if err != nil {
107 return err
108 }
109 defer r.Close()
110 if err := installer.ReadYaml(r, &env); err != nil {
111 return err
112 }
113 if err := initNewEnv(s.ss, installer.NewRepoIO(repo, s.ss.Signer), s.nsCreator, req, env); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400114 return err
115 }
116 }
117 {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400118 ssPubKey, err := s.ss.GetPublicKey()
119 if err != nil {
120 return err
121 }
122 if err := addNewEnv(
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400123 s.repo,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400124 req,
125 keys,
126 ssPubKey,
127 ); err != nil {
128 return err
129 }
130 }
131 return c.String(http.StatusOK, "OK")
132}
133
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400134func initNewEnv(
135 ss *soft.Client,
136 r installer.RepoIO,
137 nsCreator installer.NamespaceCreator,
138 req createEnvReq,
139 env installer.EnvConfig,
140) error {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400141 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400142 if err != nil {
143 return err
144 }
145 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400146 // TODO(giolekva): private domain can be configurable as well
147 config := installer.Config{
148 Values: installer.Values{
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400149 PCloudEnvName: env.Name,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400150 Id: req.Name,
151 ContactEmail: req.ContactEmail,
152 Domain: req.Domain,
153 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400154 PublicIP: env.PublicIP,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400155 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
156 },
157 }
158 if err := r.WriteYaml("config.yaml", config); err != nil {
159 return err
160 }
161 {
162 out, err := r.Writer("pcloud-charts.yaml")
163 if err != nil {
164 return err
165 }
166 defer out.Close()
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400167 _, err = out.Write([]byte(fmt.Sprintf(`
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400168apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400169kind: GitRepository
170metadata:
171 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400172 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400173spec:
174 interval: 1m0s
175 url: https://github.com/giolekva/pcloud
176 ref:
177 branch: main
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400178`, req.Name)))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400179 if err != nil {
180 return err
181 }
182 }
183 rootKust := installer.NewKustomization()
184 rootKust.AddResources("pcloud-charts.yaml", "apps")
185 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
186 return err
187 }
188 appsKust := installer.NewKustomization()
189 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
190 return err
191 }
192 r.CommitAndPush("initialize config")
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400193 nsGen := installer.NewPrefixGenerator(req.Name + "-")
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400194 suffixGen := installer.NewEmptySuffixGenerator()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400195 {
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400196 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400197 if err != nil {
198 return err
199 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400200 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400201 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
202 "From": "10.1.0.1",
203 "To": "10.1.0.1",
204 "AutoAssign": false,
205 }); err != nil {
206 return err
207 }
208 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
209 "Name": fmt.Sprintf("%s-headscale", req.Name),
210 "From": "10.1.0.2",
211 "To": "10.1.0.2",
212 "AutoAssign": false,
213 }); err != nil {
214 return err
215 }
216 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
217 "Name": fmt.Sprintf("%s-soft-serve", req.Name), // TODO(giolekva): rename to config repo
218 "From": "10.1.0.3",
219 "To": "10.1.0.3",
220 "AutoAssign": false,
221 }); err != nil {
222 return err
223 }
224 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
225 "Name": req.Name,
226 "From": "10.1.0.100",
227 "To": "10.1.0.254",
228 "AutoAssign": false,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400229 }); err != nil {
230 return err
231 }
232 }
233 {
234 app, err := appsRepo.Find("ingress-private")
235 if err != nil {
236 return err
237 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400238 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400239 return err
240 }
241 }
242 {
243 app, err := appsRepo.Find("certificate-issuer-public")
244 if err != nil {
245 return err
246 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400247 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400248 return err
249 }
250 }
251 {
252 app, err := appsRepo.Find("core-auth")
253 if err != nil {
254 return err
255 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400256 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400257 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
258 }); err != nil {
259 return err
260 }
261 }
262 {
263 app, err := appsRepo.Find("headscale")
264 if err != nil {
265 return err
266 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400267 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400268 "Subdomain": "headscale",
269 }); err != nil {
270 return err
271 }
272 }
273 {
274 keys, err := installer.NewSSHKeyPair()
275 if err != nil {
276 return err
277 }
278 user := fmt.Sprintf("%s-welcome", req.Name)
279 if err := ss.AddUser(user, keys.Public); err != nil {
280 return err
281 }
282 if err := ss.AddCollaborator(req.Name, user); err != nil {
283 return err
284 }
285 app, err := appsRepo.Find("welcome")
286 if err != nil {
287 return err
288 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400289 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400290 "RepoAddr": ss.GetRepoAddress(req.Name),
291 "SSHPrivateKey": keys.Private,
292 }); err != nil {
293 return err
294 }
295 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400296 {
297 keys, err := installer.NewSSHKeyPair()
298 if err != nil {
299 return err
300 }
301 user := fmt.Sprintf("%s-appmanager", req.Name)
302 if err := ss.AddUser(user, keys.Public); err != nil {
303 return err
304 }
305 if err := ss.AddCollaborator(req.Name, user); err != nil {
306 return err
307 }
308 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
309 if err != nil {
310 return err
311 }
312 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
313 "RepoAddr": ss.GetRepoAddress(req.Name),
314 "SSHPrivateKey": keys.Private,
315 }); err != nil {
316 return err
317 }
318 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400319 return nil
320}
321
322func addNewEnv(
323 repoIO installer.RepoIO,
324 req createEnvReq,
325 keys installer.KeyPair,
326 pcloudRepoPublicKey []byte,
327) error {
328 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
329 if err != nil {
330 return err
331 }
332 kust.AddResources(req.Name)
333 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
334 if err != nil {
335 return err
336 }
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400337 repoIP := repoIO.Addr().Addr().String()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400338 for _, tmpl := range tmpls.Templates() {
339 dstPath := path.Join("environments", req.Name, tmpl.Name())
340 dst, err := repoIO.Writer(dstPath)
341 if err != nil {
342 return err
343 }
344 defer dst.Close()
345 if err := tmpl.Execute(dst, map[string]string{
346 "Name": req.Name,
347 "PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
348 "PublicKey": base64.StdEncoding.EncodeToString([]byte(keys.Public)),
349 "GitHost": repoIP,
350 "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", repoIP, pcloudRepoPublicKey))),
351 }); err != nil {
352 return err
353 }
354 }
355 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
356 return err
357 }
358 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
359 return err
360 }
361 return nil
362}